§ 5 · Hallazgos
Veinte hallazgos — cada uno reproducible desde scripts/_security_audit.json.
F-01Crítico
CVSS v3.1 9,4 · AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H
SQL Server TCP/1433 alcanzable desde el internet público.
- Recurso
- DBServers-NSG regla
PermitMySQL · NIC dbsvr-001887 (VM DBSvr-001, PIP 203.0.113.33)
- Control
- CIS Azure Foundations v2.1.0 § 6.4 · MCSB NS-1 · NIST 800-53 SC-7
Evidencia
$ python scripts/azure_security_audit.py | jq '.network.nsgs[] | select(.name=="DBServers-NSG").rules'
[{ "name":"PermitMySQL","priority":110,"direction":"Inbound","access":"Allow",
"protocol":"TCP","srcAddr":"198.51.100.10","dstPort":"1433" }]
Compromiso
Un compromiso de la única IP de origen 198.51.100.10 (la IP doméstica de internet de un administrador) otorga acceso TCP directo a TDS 1433 en el SQL Server. Desde ahí el atacante puede: (a) intentar fuerza bruta de inicio de sesión SQL / Windows contra DBSvr-001 sin ningún límite de velocidad ni WAF en la ruta; (b) hacer fingerprinting y atacar CVE sin parchear de SQL Server (el motor es alcanzable desde hosts arbitrarios de internet en el momento en que la IP de origen se compromete o se reasigna); (c) ante cualquier autenticación exitosa, leer, modificar, eliminar o exfiltrar cualquier objeto de base de datos contra el que esté autorizada la credencial capturada — la auditoría no puede indicarle qué bases de datos contienen qué, por lo que la cota superior es "cada base de datos alojada en esta instancia"; (d) pivotar desde xp_cmdshell u otras funciones similares, si están habilitadas, para lograr ejecución de código en la VM. Las IPs residenciales suelen ser reasignadas por los ISP y con frecuencia están detrás de routers de consumo comprometidos.
Remediación · Azure CLI
# Paso 1 — confirmar que nadie depende actualmente de la ruta pública 1433
# (el Activity Log + los flow logs lo mostrarían; sin ambos, asumir que no)
# Paso 2 — eliminar la regla
az network nsg rule delete \
--resource-group Production-RG \
--nsg-name DBServers-NSG \
--name PermitMySQL
# Paso 3 — confirmar que la ruta de acceso permanece: ACC-001az (10.0.2.8) → DBSvr-001 (10.0.2.7) en 1433
# está permitida por la regla predeterminada AllowVnetInBound. El CRM sigue funcionando.
Reversión: az network nsg rule create --resource-group Production-RG --nsg-name DBServers-NSG --name PermitMySQL --priority 110 --direction Inbound --access Allow --protocol Tcp --source-address-prefixes 198.51.100.10 --destination-port-ranges 1433
Validación
az network nsg rule list --resource-group Production-RG --nsg-name DBServers-NSG \
--query "[?name=='PermitMySQL']" -o table
# Salida esperada: vacía
Radio de impacto de la corrección · Medio · reversible en segundos. Verifique con Dana que ninguna herramienta de reportes se conecte a la SQL VM desde fuera de la VNet por la IP pública.
F-02Alto
CVSS v3.1 8,1 · AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H
RDP público (TCP/3389) en cada VM desde una única IP residencial de administrador.
- Recursos
- Las 6 NSG permiten TCP/3389 de entrada desde 198.51.100.10 (una también desde 198.51.100.11). PIPs:
ACC-001az-ip, ADM-001az-ip, DBSvr-001-ip, DCSvr-001-ip, DCSvr-002-ip, LANSvr-001-ip.
- Control
- CIS Azure Foundations v2.1.0 § 6.1 · MCSB NS-2 · NIST 800-53 SC-7
Evidencia
$ jq '.network.nsgs[].rules[] | select(.dstPort=="3389")' < scripts/_security_audit.json
# Cada NSG devuelve al menos una regla de permiso
Compromiso
Una única IP residencial 198.51.100.10 (hogar del administrador) es la pieza clave de movimiento lateral para toda la flota de producción. Los compromisos comunes de routers domésticos (CVE-2024-3273 D-Link, MikroTik VPNFilter, 0-days de ISP-CPE) producen man-in-the-middle en el tráfico de salida y permitirían fuerza bruta o pass-the-hash desde una posición cuya IP de origen la NSG ya confía. Incluso sin compromiso, un ISP residencial puede reasignar esta IP a otro suscriptor tras el reinicio de un módem. Un inicio de sesión RDP exitoso en cualquier VM de esta subred proporciona entonces una posición dentro de la VNet desde la cual la regla predeterminada de Azure AllowVnetInBound permite movimiento este-oeste sin restricción hacia cualquier otra VM, incluidas la SQL VM y ambos controladores de dominio (véase § 4 — no hay microsegmentación).
Remediación · Azure CLI
# REQUISITO PREVIO: Bastion está aprovisionado (Contoso_Bastion existe) — verifique el acceso vía Bastion
# a ACC-001az y ADM-001az ANTES de eliminar la ruta de IP pública.
# Paso 1 — Verificar que la ruta de bastion funciona para al menos un administrador
# (verificación manual — Dana entra por RDP a través de https://portal.azure.com → Bastion)
# Paso 2 — Eliminar las reglas RDP por NSG
for nsg in ACC-001az-nsg ADM-001az-nsg DBServers-NSG DomainControllers-NSG Servers-NSG; do
az network nsg rule list -g Production-RG --nsg-name $nsg \
--query "[?destinationPortRange=='3389'].name" -o tsv | \
xargs -I{} az network nsg rule delete -g Production-RG --nsg-name $nsg --name {}
done
# Paso 3 — Desasociar las 6 IPs públicas administrativas de sus NIC
for ip in ACC-001az-ip ADM-001az-ip DBSvr-001-ip DCSvr-001-ip DCSvr-002-ip LANSvr-001-ip; do
nic_ip_config=$(az network public-ip show -g Production-RG -n $ip --query ipConfiguration.id -o tsv)
nic=$(echo $nic_ip_config | awk -F'/' '{print $9}')
cfg=$(echo $nic_ip_config | awk -F'/' '{print $11}')
az network nic ip-config update -g Production-RG --nic-name $nic --name $cfg --remove publicIpAddress
done
# Paso 4 — Opcionalmente eliminar los objetos de IP pública tras la desasociación
# (hágalo tras una retención suave de 30 días para confirmar que no hace falta reversión)
Reversión: vuelva a crear las reglas de NSG (prioridad 100, src 198.51.100.10, puerto 3389), luego reasocie cada PIP al ipconfig1 de su NIC.
Validación
az network nsg rule list -g Production-RG --nsg-name DBServers-NSG \
--query "[?destinationPortRange=='3389']" -o table
# Esperado: vacío en las 5 NSG
Radio de impacto de la corrección · Alto · el acceso administrativo depende por completo de que Bastion funcione. No lo ejecute sin una ruta de Bastion verificada para cada operador Y una alternativa break-glass verificada (p. ej., la IP de Dana en una allowlist temporal por 48 horas).
F-03Alto
CVSS v3.1 7,5 · AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:N/A:N
El enableShareableLink de Azure Bastion está en true.
- Recurso
/subscriptions/a1b2c3d4-…/resourceGroups/Production-RG/providers/Microsoft.Network/bastionHosts/Contoso_Bastion
- Control
- MCSB IM-1, IM-3 · NIST 800-53 IA-2(1) · CIS Azure § 6.x (a nivel de red)
Evidencia
$ jq '.bastion_hosts_full.value[0].properties | {enableShareableLink, enableIpConnect, enableTunneling, enableKerberos, scaleUnits}' < scripts/_security_extra.json
{
"enableShareableLink": true,
"enableIpConnect": true,
"enableTunneling": true,
"enableKerberos": false,
"scaleUnits": 2
}
Compromiso
Shareable Link permite que un usuario de Azure con el permiso adecuado de Bastion genere una URL que otorga acceso RDP / SSH a una VM específica. La URL puede reenviarse a cualquiera — incluidos usuarios sin cuenta de Entra ID, sin evaluación de Conditional Access y sin MFA. El error de un solo usuario privilegiado (enlace mal compartido), un compromiso de credenciales o una acción interna filtra el acceso a una VM de producción mediante una URL copiable y pegable. La identidad del destinatario del enlace no se autentica contra el tenant, por lo que toda la actividad posterior a través de esa sesión es atribuible únicamente a la entrada de log de Bastion y al usuario emisor — lo que dificulta la atribución y elude los controles de autenticación fuerte que normalmente condicionan el acceso a Azure.
Remediación · Azure CLI
az network bastion update --resource-group Production-RG --name Contoso_Bastion \
--enable-shareable-link false
Reversión: az network bastion update --resource-group Production-RG --name Contoso_Bastion --enable-shareable-link true
Validación
az network bastion show -g Production-RG -n Contoso_Bastion \
--query "{shareable:enableShareableLink,ipConnect:enableIpConnect,tunneling:enableTunneling}" -o table
# Esperado: shareable=false
Radio de impacto de la corrección · Bajo · solo rompe el flujo de compartir por enlace. Las conexiones estándar de Bastion basadas en navegador siguen funcionando. Considere también deshabilitar enableIpConnect a menos que el flujo requiera específicamente conectarse a direcciones IP fuera de la VNet.
F-04Alto
DREAD 16 / 25 · D2 R3 E3 A4 D4
Defender for Cloud — todos los planes de pago están deshabilitados.
- Recurso
- Pricings a nivel de suscripción en
Microsoft.Security/pricings
- Control
- CIS Azure Foundations v2.1.0 § 2.1.1–2.1.16 · MCSB DS-2, GS-1 · NIST 800-53 SI-4
Evidencia
$ jq '.defender_pricings[] | select(.pricing_tier=="Free") | .name' < scripts/_security_extra.json
"VirtualMachines" # Defender for Servers — deshabilitado
"SqlServers" # Defender for SQL — deshabilitado
"SqlServerVirtualMachines" # Defender for SQL en máquinas — deshabilitado ← protege la BD del CRM
"StorageAccounts" # Defender for Storage — deshabilitado
"KeyVaults" # Defender for Key Vault — deshabilitado
"Arm" # Defender for ARM — deshabilitado
"Dns" # Defender for DNS — deshabilitado
…
$ jq '.defender_secure_scores[0] | {current,max,percentage}' < scripts/_security_extra.json
{ "current": 16.8, "max": 36, "percentage": 0.4667 }
Compromiso
No hay detección de amenazas por comportamiento en el SQL Server (patrones de consulta sospechosos, señales de inyección SQL, fuerza bruta en inicios de sesión, lecturas de volumen de datos anómalas), no hay señalización de malware / EDR en las VM (Defender for Servers P2 incluye Defender for Endpoint sin licencia adicional), no hay detección de compromiso en ARM (p. ej., concesiones de roles sospechosas o patrones de creación de recursos desde una IP nueva), y no hay JIT VM Access (que requiere Defender for Servers). Un atacante que opera con credenciales capturadas dentro de esta suscripción no recibe ninguna alerta, no deja ninguna señal con grado de detección y no enfrenta ninguna contención automatizada. El Secure Score del 46,67 % refleja esto.
Remediación · Azure CLI
# Habilitar Defender for SQL en SQL VMs (plan de mayor valor para este entorno)
az security pricing create --name SqlServerVirtualMachines --tier 'Standard'
# Habilitar Defender for Servers Plan 2 (incluye MDE, JIT, monitoreo de integridad de archivos)
az security pricing create --name VirtualMachines --tier 'Standard' --subplan 'P2'
# Habilitar Defender for ARM (alerta sobre actividad sospechosa del plano de gestión)
az security pricing create --name Arm --tier 'Standard'
# Habilitar Defender for DNS (detecta C2 basado en DNS)
az security pricing create --name Dns --tier 'Standard'
# Habilitar Defender for Key Vault (cuando se despliegue el KV — véase F-12)
az security pricing create --name KeyVaults --tier 'Standard'
Costo mensual indicativo: Defender for SQL en una sola VM ≈ US$ 15, Defender for Servers P2 ≈ US$ 15/VM × 6 = US$ 90, Defender for ARM ≈ US$ 4 por millón de operaciones ARM (≈ US$ 5/mes para esta escala), Defender for DNS ≈ US$ 0,70 / 1M de consultas. Total ≈ US$ 115–130 / mes para una línea base de seguridad integral.
Reversión: az security pricing create --name <plan> --tier 'Free'
Validación
az security pricing list \
--query "value[?contains('VirtualMachines SqlServerVirtualMachines Arm Dns',name)].{name:name,tier:pricingTier}" -o table
# Esperado: todos en 'Standard'
Radio de impacto de la corrección · Bajo · los planes de Defender funcionan como monitoreo de la plataforma sin huella de agente en las VM (Defender for Servers P2 despliega MDE, que se comporta bien en Windows Server 2025).
F-05Alto
DREAD 17 / 25 · D3 R4 E2 A5 D3
Sin diagnostic settings en ningún recurso en alcance; el Activity Log no se exporta.
- Recursos
- Todas las NSG, todas las VM, la VNet, el VPN Gateway, el Recovery Vault, el propio ámbito de la suscripción
- Control
- CIS Azure v2.1.0 § 5.1.1, § 5.3, § 5.4 · MCSB LT-3, LT-4 · NIST 800-53 AU-2, AU-6, AU-11
Evidencia
$ jq '.sub_activity_log_diag_settings.value | length' < scripts/_security_extra.json
0
$ jq '.resource_diag_settings[] | {r:.resource, count:(.body.value|length)}' < scripts/_security_extra.json
# Los 11 recursos devuelven count=0
Compromiso
La retención del Activity Log es de forma predeterminada 90 días en la suscripción, tras los cuales se purga. Los logs de recursos (aciertos de reglas de NSG, eventos de túnel del VPN gateway, eventos de eliminación / recuperación de vaults, registros de flujo de NIC) no se retienen en absoluto. Un adversario que opera con la lentitud suficiente para dejar pasar la ventana de 90 días no deja ningún rastro recuperable de las acciones del plano de gestión; un adversario que opera de forma ruidosa deja rastros que cualquiera con acceso al Activity Log puede eliminar en sitio (sin copia inmutable). La reconstrucción forense de una brecha está limitada por la telemetría de la plataforma que sobrevive en la ventana de retención predeterminada, y ninguna investigación interna puede mapear "quién cambió la NSG el día X" más allá de 90 días. Esto también impide cumplir cualquier obligación de "retención de logs de auditoría ≥ 1 año" según NIST 800-53 AU-11.
Remediación · Bicep
// 1. Crear un workspace central de Log Analytics
resource law 'Microsoft.OperationalInsights/workspaces@2023-09-01' = {
name: 'contoso-security-law'
location: 'eastus'
properties: {
sku: { name: 'PerGB2018' }
retentionInDays: 90 // retención en caliente; el largo plazo va a storage
features: { immediatePurgeDataOn30Days: false }
}
}
// 2. Activity Log a nivel de suscripción → workspace
resource subDiag 'Microsoft.Insights/diagnosticSettings@2021-05-01-preview' = {
name: 'send-activity-log'
scope: subscription()
properties: {
workspaceId: law.id
logs: [
{ category: 'Administrative', enabled: true }
{ category: 'Security', enabled: true }
{ category: 'ServiceHealth', enabled: true }
{ category: 'Alert', enabled: true }
{ category: 'Recommendation', enabled: true }
{ category: 'Policy', enabled: true }
{ category: 'Autoscale', enabled: true }
{ category: 'ResourceHealth', enabled: true }
]
}
}
// 3. Aplicar diagnostic settings por recurso — lo más sencillo como una iniciativa de Azure Policy
// "Configure Azure Monitor for resources" asignada a nivel de suscripción.
Costo indicativo: el pricing de LA PerGB2018 es de US$ 2,76 / GB ingerido (eastus). Para esta escala: aciertos de NSG + Activity Log + métricas de VM ≈ 5–15 GB / mes = US$ 14–41 / mes.
Reversión: az resource delete --ids <diagnosticSettings_id> por recurso. El propio workspace usa soft-delete de 31 días (recuperable).
Validación
az monitor diagnostic-settings list --resource /subscriptions/a1b2c3d4-… -o table
# Debería devolver una entrada (send-activity-log)
Radio de impacto de la corrección · Bajo · los diagnostic settings son observadores pasivos; no pueden afectar el rendimiento de la carga de trabajo.
F-06Alto
DREAD 17 / 25 · D3 R4 E2 A5 D3
Cero alertas del Activity Log.
- Alcance
- Toda la suscripción
- Control
- CIS Azure v2.1.0 § 5.2.1–5.2.9 · MCSB IR-3 · NIST 800-53 IR-4, SI-4
Evidencia
$ jq '.activity_log_alerts_raw.value | length' < scripts/_security_extra.json
0
Compromiso
No hay detección en tiempo real de las operaciones del plano de control que un atacante usa para ampliar el acceso, suprimir la detección o destruir evidencia: Microsoft.Authorization/roleAssignments/write (escalada de privilegios), Microsoft.Network/networkSecurityGroups/securityRules/delete (desmantelamiento del firewall), Microsoft.RecoveryServices/vaults/delete (destrucción de copias de seguridad), Microsoft.Compute/virtualMachines/delete (VM de producción destruida), Microsoft.KeyVault/vaults/delete (KV destruido), Microsoft.Security/pricings/write (plan de Defender deshabilitado para suprimir alertas). La primera oportunidad del defensor de notar cualquiera de estas es la siguiente visita manual al portal — para entonces la acción ya está completa.
Remediación · Azure CLI
# Crear un action group (email a Dana + opcionalmente SMS / webhook de Teams)
az monitor action-group create -g Production-RG -n SecurityAlerts \
--short-name SecAlert \
--email-receivers name=dana email=dana.okoye@contoso.com
AG=$(az monitor action-group show -g Production-RG -n SecurityAlerts --query id -o tsv)
SUB=/subscriptions/a1b2c3d4-1111-4a22-9c33-0d44e55f6a77
# Alerta: escritura de asignación de rol
az monitor activity-log alert create -g Production-RG -n alert-role-assignment-write \
--scope $SUB --action $AG \
--condition category=Administrative \
--condition operationName=Microsoft.Authorization/roleAssignments/write
# Alerta: eliminación de regla de NSG
az monitor activity-log alert create -g Production-RG -n alert-nsg-rule-delete \
--scope $SUB --action $AG \
--condition category=Administrative \
--condition operationName=Microsoft.Network/networkSecurityGroups/securityRules/delete
# Alerta: eliminación de Recovery Services vault
az monitor activity-log alert create -g Production-RG -n alert-vault-delete \
--scope $SUB --action $AG \
--condition category=Administrative \
--condition operationName=Microsoft.RecoveryServices/vaults/delete
# Alerta: cambio de nivel de pricing de Defender (downgrade)
az monitor activity-log alert create -g Production-RG -n alert-defender-pricing-change \
--scope $SUB --action $AG \
--condition category=Administrative \
--condition operationName=Microsoft.Security/pricings/write
# Alerta: eliminación de Key Vault (después de implementar F-12)
# Alerta: eliminación de VM en la capa de producción
Reversión: az monitor activity-log alert delete -g Production-RG -n <name>
Validación
az monitor activity-log alert list -g Production-RG --query "[].{name:name,enabled:enabled}" -o table
# Esperado: ≥ 4 alertas, todas habilitadas
Radio de impacto de la corrección · Bajo · notificación puramente pasiva.
F-07Alto
CVSS v3.1 7,5 · AV:N/AC:L/PR:H/UI:N/S:C/C:N/I:H/A:H
Recovery Services vault — inmutabilidad y autorización multiusuario deshabilitadas.
- Recurso
/subscriptions/a1b2c3d4-…/resourceGroups/Production-RG/providers/Microsoft.RecoveryServices/vaults/Production-Vault
- Control
- MCSB DP-9 · NIST 800-53 CP-9, CP-10 · CIS Azure v2.1.0 § 7
Evidencia
$ jq '.production_vault.properties.securitySettings' < scripts/_security_extra.json
{
"immutabilitySettings": { "state": null }, ← OFF
"softDeleteSettings": { "softDeleteState":"Enabled", "softDeleteRetentionPeriodInDays": 14 },
"multiUserAuthorization": "Disabled" ← OFF
}
Compromiso
Con la inmutabilidad desactivada y la autorización multiusuario desactivada, un atacante que adquiera Backup Contributor o superior puede acortar la retención, deshabilitar trabajos de copia de seguridad, detener la protección de elementos protegidos y — tras dejar pasar la ventana de soft-delete de 14 días — purgar por completo los puntos de recuperación. El soft-delete defiende contra la eliminación accidental pero no contra un adversario dispuesto a esperar, y el mismo RBAC que destruye también puede acortar la ventana de soft-delete. La recuperación tras una intrusión destructiva exitosa contra las VM de producción no tendría, tras esto, ninguna capa de copia de seguridad a la que recurrir.
Remediación · Azure CLI
# Paso 1 — Habilitar la inmutabilidad primero en modo "Unlocked" (aún reversible)
az backup vault update -g Production-RG -n Production-Vault \
--immutability-state Unlocked
# Paso 2 — Tras confirmar que todas las copias de seguridad, políticas de retención y el
# instrumental operativo son correctos durante ≥ 2 semanas, bloquear la inmutabilidad:
# az backup vault update -g Production-RG -n Production-Vault \
# --immutability-state Locked
# ⚠ Una vez en Locked, la inmutabilidad no puede deshabilitarse. Esto es intencional —
# es la propiedad que hace que la capa de copia de seguridad sea intocable por ransomware.
# Paso 3 — Habilitar la autorización multiusuario (MUA): requiere un Resource Guard
az resource-guard create -g Production-RG -n contoso-rg-guard --location eastus
GUARD_ID=$(az resource-guard show -g Production-RG -n contoso-rg-guard --query id -o tsv)
az backup vault resource-guard-mapping update -g Production-RG -n Production-Vault \
--resource-guard-id $GUARD_ID
Reversión: az backup vault update --immutability-state Disabled (solo válido antes de Locked) + az backup vault resource-guard-mapping delete.
Validación
az backup vault show -g Production-RG -n Production-Vault \
--query "properties.securitySettings.{imm:immutabilitySettings.state, mua:multiUserAuthorization}" -o table
# Esperado: imm=Unlocked (luego Locked tras 2 semanas), mua=Enabled
Radio de impacto de la corrección · Medio · la inmutabilidad Unlocked es reversible; Locked es permanente (por diseño). MUA impone un flujo de aprobación de dos partes sobre las operaciones destructivas de copia de seguridad — capacite a los operadores antes de habilitarla.
F-08Alto
CVSS v3.1 7,5 · divulgación de información + amplificación
DNS (TCP + UDP 53) abierto al internet público en los DC y LANSvr.
- Recursos
DomainControllers-NSG reglas 1010 / 1020, Servers-NSG reglas 1010 / 1020. VM afectadas: DCSvr-001, DCSvr-002, LANSvr-001.
- Control
- CIS Azure v2.1.0 § 6.x · MCSB NS-2 · NIST 800-53 SC-7, SC-22
Evidencia
$ jq '.network.nsgs[].rules[] | select(.dstPort=="53")' < scripts/_security_audit.json
[
{ "name":"PermitDNS-TCP","srcAddr":"*","direction":"Inbound","access":"Allow","protocol":"TCP","dstPort":"53" },
{ "name":"PermitDNS-UDP","srcAddr":"*","direction":"Inbound","access":"Allow","protocol":"UDP","dstPort":"53" }
… // repetido para Servers-NSG
]
Compromiso
Dos riesgos distintos. (a) Resolver abierto recursivo — AD-DNS responde de forma predeterminada cualquier consulta; los atacantes lo explotan para ataques de reflexión por amplificación de DNS contra terceros, siendo la percepción del tercero sobre el origen del ataque el espacio de IPs de esta suscripción (violación de NIST SC-22, listada en la guía de mitigación de DDoS). (b) Divulgación de zona integrada en AD — los registros de corp.contoso.com (cada servidor, cada estación de trabajo, cada service principal name registrado en AD-DNS) pasan a ser enumerables por cualquiera en internet mediante consultas directas, proporcionando un mapa completo de reconocimiento del patrimonio interno antes de que se intente autenticación alguna. dig @203.0.113.11 corp.contoso.com AXFR puede tener éxito o no según las ACL de transferencia de zona de AD-DNS, pero el sondeo de registros individuales siempre funciona.
Remediación · Azure CLI
# Estos puertos nunca deberían exponerse externamente; AD-DNS debería atender a los clientes vía la VPN
# o vía Private DNS Resolver, no directamente.
for nsg in DomainControllers-NSG Servers-NSG; do
az network nsg rule delete -g Production-RG --nsg-name $nsg --name PermitDNS-TCP
az network nsg rule delete -g Production-RG --nsg-name $nsg --name PermitDNS-UDP
done
# Confirmar que las VM internas siguen resolviendo vía los DC (la regla intra-VNet AllowVnetInBound lo permite)
Reversión: vuelva a crear las reglas con las mismas prioridades y srcPrefix=*.
Validación
for nsg in DomainControllers-NSG Servers-NSG; do
az network nsg rule list -g Production-RG --nsg-name $nsg \
--query "[?destinationPortRange=='53']" -o table
done
# Esperado: vacío
Radio de impacto de la corrección · Medio · verifique que ningún servidor on-premises apunte a las IPs públicas de los DC para resolución DNS antes de aplicar. La ruta adecuada para on-premises es a través del túnel VPN S2S (actualmente 0 bytes — por lo que es poco probable que esto rompa algo en la práctica, pero confírmelo con Dana).
F-09Alto
A nivel de diseño · hallazgo CIS
Los controladores de dominio y el servidor SQL tienen IPs públicas.
- Recursos
DCSvr-001-ip, DCSvr-002-ip, DBSvr-001-ip, LANSvr-001-ip
- Control
- CIS Azure v2.1.0 § 6.6 · MCSB NS-1 · NIST 800-53 SC-7
Evidencia: véase la tabla de § 4 IPs públicas.
Compromiso
La capa de identidad (DC) y la capa de datos (BD) nunca deberían llevar IPs públicas en un entorno de producción. Incluso con la NSG acotada a una única IP de origen, la existencia de la asociación significa que: (1) cualquier error en una regla de NSG expone el recurso de inmediato a internet — un solo "Any" tecleado por error en una edición futura de una regla está a un clic de la exposición total; (2) el recurso es enumerable por escáneres de todo internet (Shodan, Censys) y aparece en sus conjuntos de datos públicos sin importar el estado de la NSG; (3) un proceso que se ejecuta en el host y que se enlaza a su interfaz de cara al público (intencionalmente o por mala configuración) obtiene una ruta de salida que elude cualquier futuro Azure Firewall / NAT Gateway diseñado para centralizar la inspección de egress.
Remediación: véase F-02 Paso 3 — las cuatro IPs públicas se desasocian como parte de esa corrección una vez validados Bastion + VPN-P2S.
Validación
az network nic show -g Production-RG -n dbsvr-001887 \
--query "ipConfigurations[].publicIPAddress" -o tsv
# Esperado: vacío
Radio de impacto de la corrección · Alto · condicionado a F-02 (Bastion + VPN listos).
F-10Alto (operativo)
DREAD 17 / 25 · D2 R3 E5 A4 D3
La autenticación P2S del gateway VPN está sin configurar (bloqueo operativo).
- Recurso
/subscriptions/a1b2c3d4-…/resourceGroups/Production-RG/providers/Microsoft.Network/virtualNetworkGateways/Azure-ContosoNet
- Control
- MCSB IM-1 · NIST 800-53 IA-2(11)
Evidencia
$ jq '.virtual_network_gateways.value[0].properties.vpnClientConfiguration | {protocols:vpnClientProtocols, auth:vpnAuthenticationTypes, rootCerts:(vpnClientRootCertificates|length), aad:aadTenant, radius:radiusServerAddress}' < scripts/_security_extra.json
{
"protocols": ["OpenVPN","IkeV2"],
"auth": [],
"rootCerts": 0,
"aad": null,
"radius": null
}
Compromiso
Los trabajadores remotos no pueden usar la VPN — los protocolos están definidos pero no hay ningún método de autenticación configurado, por lo que ningún cliente puede completar un handshake. Esto obliga a usar la ruta RDP directa por IP pública de la VM (F-02), que es la mayor superficie de ataque actual. El despliegue de Bastion mitiga esto parcialmente para sesiones desde dispositivos que ya tienen una sesión del portal de Azure, pero no hace nada por los endpoints no administrados que necesitan acceso directo dentro de la VNet. El compromiso que esto crea es estructural: la organización no puede retirar la superficie RDP de IP pública (F-02 / F-09) sin entregar primero esta ruta de autenticación VPN, por lo que cada día que pasa sin autenticación P2S de Entra ID es otro día en que las seis IPs públicas de las VM deben permanecer asociadas.
Remediación · Terraform
resource "azurerm_virtual_network_gateway" "contosonet" {
# ... configuración existente ...
vpn_client_configuration {
address_space = ["172.16.201.0/24"]
vpn_client_protocols = ["OpenVPN"]
vpn_auth_types = ["AAD"]
aad_tenant = "https://login.microsoftonline.com/e7f8a9b0-2222-4b33-8d44-1e55f6607b88/"
aad_audience = "c632b3df-fb67-4d84-bdcf-b95ad541b5c8" # app AAD del cliente VPN de Azure
aad_issuer = "https://sts.windows.net/e7f8a9b0-2222-4b33-8d44-1e55f6607b88/"
}
}
Requisito previo: una política de Conditional Access dirigida a la aplicación VPN de Azure que exija MFA + dispositivo conforme.
Reversión: elimine el bloque vpn_client_configuration.
Validación
az network vnet-gateway show -g Production-RG -n Azure-ContosoNet \
--query "vpnClientConfiguration.{auth:vpnAuthenticationTypes, aad:aadTenant}" -o table
# Esperado: auth=AAD, aad=https://login.microsoftonline.com/e7f8a9b0-…/
Radio de impacto de la corrección · Medio · habilita una nueva ruta de entrada; verifique que la política de Conditional Access sea correcta antes de anunciar el acceso a los usuarios.
F-11Medio
DREAD 14 / 25 · D2 R3 E3 A3 D3
NSG Flow Logs deshabilitados — sin visibilidad de la anomalía de egress.
- Recursos
- Las 5 NSG (sin flow logs en Network Watcher)
- Control
- CIS Azure v2.1.0 § 6.5 · MCSB LT-3 · NIST 800-53 AU-12
Evidencia
$ jq '.flow_logs.value | length' < scripts/_security_extra.json
0
Compromiso
La anomalía de egress de red saliente de los DC (910 GB / mes en DC1, 640 GB / mes en DC2 — véase dc-performance-20260514.md) no puede caracterizarse sin flow logs. Podemos ver cuánto está saliendo pero no hacia dónde. Esto bloquea tanto la cuestión de costo (mover el destino de la copia de seguridad a la misma región si corresponde) como la de seguridad — solo con las métricas del host no podemos distinguir "AzureBackup subiendo a un vault" de "un proceso no identificado exfiltrando datos". Los 21–40 GB / día de salida por DC están dos o tres órdenes de magnitud por encima de lo esperado para un controlador de dominio, y sin flow logs las IPs de destino son imposibles de conocer desde fuera del SO invitado.
Remediación · Azure CLI
# Crear una storage account para los flow logs (eastus, LRS Standard)
az storage account create -g Production-RG -n contosoflowlogs$RANDOM \
--location eastus --sku Standard_LRS --kind StorageV2 \
--min-tls-version TLS1_2 --allow-blob-public-access false
SA=$(az storage account list -g Production-RG --query "[?starts_with(name,'contosoflowlogs')].id" -o tsv)
for nsg in ACC-001az-nsg ADM-001az-nsg DBServers-NSG DomainControllers-NSG Servers-NSG; do
NSG_ID=$(az network nsg show -g Production-RG -n $nsg --query id -o tsv)
az network watcher flow-log create -l eastus -n flowlog-$nsg \
--nsg $NSG_ID --storage-account $SA --retention 90
done
# Opcionalmente habilitar Traffic Analytics (requiere un workspace de Log Analytics)
Reversión: az network watcher flow-log delete -l eastus -n flowlog-<nsg>
Validación
az network watcher flow-log list -l eastus -o table
# Esperado: 5 entradas, enabled=True
Radio de impacto de la corrección · Bajo · los flow logs son pasivos.
F-12Medio
DREAD 14 / 25 · D3 R3 E2 A4 D2
Sin Key Vault desplegado — los secretos y certificados no se gestionan de forma centralizada.
- Alcance
- Toda la suscripción (existen cero
Microsoft.KeyVault/vaults)
- Control
- CIS Azure v2.1.0 § 8 · MCSB DS-6 · NIST 800-53 SC-12, SC-28
Evidencia
$ jq '.key_vaults | length' < scripts/_security_audit.json
0
Compromiso
No hay un almacén de secretos central, auditado y controlado por RBAC. Todo material secreto actualmente en uso vive donde el operador lo haya colocado — archivos .env en las laptops de los operadores, archivos de configuración en las VM, o inline en la propia definición del recurso. Ejemplos concretos conocidos:
- El client secret del SP de auditoría vive actualmente en
.env en la laptop del consultor (AZURE_CLIENT_SECRET=…).
- La clave precompartida IPSec para la conexión S2S
ContosoNet (sharedKey: false en la respuesta de la API — o bien actualmente nula o almacenada fuera de Azure).
- La conexión
azurevm de Logic App contiene la credencial que requiera su conector V1.
- Cualquier credencial de aplicación usada por las cargas de trabajo en las VM se almacena en la configuración local que esas cargas de trabajo esperen.
Sin un Key Vault no hay ciclo de vida de rotación, ni telemetría de uso, ni control de acceso acotado sobre estas credenciales, ni un lugar donde poner futuras credenciales (clave TDE de Defender for SQL, CMK para storage o discos una vez que exista Key Vault).
Remediación · Bicep
resource kv 'Microsoft.KeyVault/vaults@2024-04-01-preview' = {
name: 'contoso-prod-kv-${uniqueString(resourceGroup().id)}'
location: 'eastus'
properties: {
tenantId: subscription().tenantId
sku: { family: 'A', name: 'standard' }
enableRbacAuthorization: true // NO access policies
enableSoftDelete: true
softDeleteRetentionInDays: 90
enablePurgeProtection: true
publicNetworkAccess: 'Disabled'
networkAcls: {
defaultAction: 'Deny'
bypass: 'AzureServices'
}
}
}
// Además, un Private Endpoint hacia vNet-001 para acceso en runtime desde las VM.
Reversión: az keyvault delete (soft-delete de 90 días recuperable, la purge-protection fuerza la recuperación).
Validación
az keyvault list --query "[].{name:name, rbac:properties.enableRbacAuthorization, pna:properties.publicNetworkAccess, purge:properties.enablePurgeProtection}" -o table
# Esperado: rbac=true, pna=Disabled, purge=true
Radio de impacto de la corrección · Bajo · solo un recurso nuevo.
F-13Medio
DREAD 14 / 25 · D4 R2 E2 A4 D2
Sin resource locks en los recursos de producción.
- Alcance
- Todos los recursos (la consulta a nivel de suscripción devolvió 0 locks)
- Control
- CIS Azure v2.1.0 § 10 · MCSB GS-6
Evidencia
$ jq '.locks.value | length' < scripts/_security_extra.json
0
Impacto. Una sola acción mal cliqueada en el portal de Azure por cualquiera con Contributor o superior (actualmente: 1 usuario) elimina una VM de producción, la SQL VM o el único par de controladores de dominio. Los resource locks en CanNotDelete impiden que la llamada a la API tenga éxito sin importar el RBAC.
Remediación · Azure CLI
# Bloquear todo el grupo de recursos de producción contra eliminación
az lock create -g Production-RG --name "no-delete-production" --lock-type CanNotDelete \
--notes "Production environment — break-glass change required to delete"
# Opcionalmente bloquear recursos individuales de alto valor
for r in DBSvr-001 DCSvr-001 DCSvr-002 Production-Vault Azure-ContosoNet Contoso_Bastion; do
az lock create --resource-group Production-RG --resource-name $r \
--resource-type <type> --name "no-delete-$r" --lock-type CanNotDelete
done
Reversión: az lock delete --name no-delete-production --resource-group Production-RG
Validación
az lock list -g Production-RG -o table
# Esperado: 1+ entradas de lock
Radio de impacto de la corrección · Bajo · los locks solo bloquean operaciones destructivas; no afectan el runtime.
F-14Medio
DREAD 13 / 25 · D3 R3 E2 A3 D2
Dos usuarios tienen Owner de la suscripción; sin PIM / sin distinción break-glass.
- Alcance
- Asignaciones de roles a nivel de suscripción
- Control
- CIS Azure v2.1.0 § 1.21, § 1.23 · MCSB PA-1, PA-2 · NIST 800-53 AC-6(5)
Evidencia
IDs de roles decodificados — 8e3af657-… = Owner, b24988ac-… = Contributor, acdd72a7-… = Reader
Owner (ámbito sub): 7a8b9c0d-7777-4088-9299-633aebb5fa43 (User)
Owner (ámbito sub): 6f7a8b9c-6666-4f77-8188-5229daa4ef32 (User) ← también Reader en el mismo ámbito (redundante)
Contributor (ámbito sub): 8b9c0d1e-8888-4199-a3aa-744bfcc60b54 (User)
Reader (ámbito sub): 9c0d1e2f-9999-42aa-b4bb-855c0dd71c65 (SP — identidad de esta auditoría)
Impacto. Dos Owners permanentes de la suscripción significan dos puntos donde el compromiso de credenciales produce la toma total del entorno. Se desconoce el Conditional Access de esos usuarios (el SP carece de Directory.Read.All — véase F-15). Uno de los dos owners (6f7a8b9c-…) también tiene Reader en el mismo ámbito, lo cual es peso muerto (Owner ya otorga lectura).
Remediación
Requiere Entra ID Premium P2 para PIM. Sin P2:
# Paso 1 — Auditar quiénes son los dos GUID de Owner (requiere GA de Entra ID o un usuario con User.Read.All)
az ad user show --id 7a8b9c0d-7777-4088-9299-633aebb5fa43 -o table
az ad user show --id 6f7a8b9c-6666-4f77-8188-5229daa4ef32 -o table
# Paso 2 — Eliminar el Reader redundante en 6f7a8b9c-…
ASSIGN_ID=$(az role assignment list --assignee 6f7a8b9c-6666-4f77-8188-5229daa4ef32 \
--role Reader --scope /subscriptions/a1b2c3d4-… --query "[0].id" -o tsv)
az role assignment delete --ids $ASSIGN_ID
# Paso 3 — Confirmar que ambos Owners tengan una cuenta break-glass designada correspondiente
# y una política de Conditional Access que exija MFA + dispositivo conforme.
Reversión: vuelva a crear la asignación de rol mediante az role assignment create.
Validación
az role assignment list --scope /subscriptions/a1b2c3d4-… --role Owner -o table
# Esperado: 2 entradas (o 1 + break-glass) — no más
Radio de impacto de la corrección · de Bajo (eliminar el Reader redundante) a Alto (modificar las relaciones reales de Owner — coordine con Dana).
F-15Informativo
Evidencia requerida
Lectura del directorio de Entra ID denegada a la identidad de auditoría.
- Recurso
- El propio SP de auditoría (
0c1d2e3f-3333-4c44-9e55-2f66a7718c99)
Evidencia
$ jq '.entra' < scripts/_security_audit.json
{
"service_principals_count_status": 200,
"directory_roles_status": 403,
"conditional_access_status": 403,
"security_defaults_status": 403,
"conditional_access_error": "AccessDenied: required scopes are missing in the token"
}
Impacto. La auditoría no puede responder 8 de los controles de identidad de CIS § 1.x sin lectura del directorio de Entra ID:
- § 1.1 — security defaults habilitados
- § 1.2–1.13 — MFA, autenticación heredada, cobertura de Conditional Access
- § 1.21–1.22 — restricciones de invitación de invitados
Estos son controles de identidad fundamentales. Sin evidencia, se marcan como Evidence required en lugar de Pass o Fail.
Remediación
- Otorgue al SP de auditoría Global Reader (recomendado — la lectura más amplia, cero escritura), O
- Permisos de aplicación
Directory.Read.All + Policy.Read.All en Microsoft Graph (requiere consentimiento de administrador).
Validación: vuelva a ejecutar scripts/azure_security_audit.py y confirme entra.conditional_access_status: 200 y una lista directory_roles no vacía.
Radio de impacto de la corrección · Bajo · concesión de permiso de solo lectura.
F-16Medio
DREAD 12 / 25 · D2 R2 E3 A3 D2
Las VM carecen de identidades administradas; las credenciales del SP se almacenan en .env.
- Recursos
- Las 6 VM (
identity: None)
- Control
- MCSB IM-3 · NIST 800-53 IA-5(11) · CIS Azure § 1.20
Evidencia
$ jq '.compute.vms[] | {name, identity}' < scripts/_security_audit.json
{ "name":"DBSvr-001","identity":null } ← repetido para las 6
$ jq '.network.flow_logs.value | length' < scripts/_security_extra.json
0 ← sin traza de auditoría del uso de secretos en ausencia de Defender for ARM + flow logs
Impacto. Cualquier llamada de una carga de trabajo a la API de Azure desde una VM debe usar (a) un secreto de SP almacenado (el patrón actual — el secreto vive en archivos .env distribuidos por las laptops de los operadores), o (b) una credencial de usuario almacenada. La asignación de una identidad administrada a una VM le da a la carga de trabajo un token emitido por Entra que rota automáticamente, se acota vía RBAC y nunca aparece en ningún archivo. El secreto del SP de auditoría ya ha quedado expuesto a múltiples sesiones de Claude (véase el informe de identidad del CRM, sección "Side fix").
Remediación · Azure CLI
# Habilitar la identidad administrada asignada por el sistema en cada VM
for vm in DBSvr-001 ADM-001az ACC-001az DCSvr-001 DCSvr-002 LANSvr-001; do
az vm identity assign -g Production-RG -n $vm
done
# Luego migrar las cargas de trabajo para usar la identidad (depende de qué se ejecute en cada VM).
# Rotar el secreto del SP de auditoría en paralelo.
Reversión: az vm identity remove -g Production-RG -n $vm
Validación
az vm list -g Production-RG --query "[].{name:name,identity:identity.type}" -o table
# Esperado: SystemAssigned para cada VM
Radio de impacto de la corrección · Bajo · agregar la identidad no es disruptivo; la migración de la carga de trabajo que la usa se acota por separado.
F-17Bajo
DREAD 8 / 25 · D2 R2 E1 A2 D1
Sin DDoS Protection Standard en la VNet que aloja cargas de trabajo de cara al público.
- Recurso
vNet-001 · enableDdosProtection: false
- Control
- CIS Azure v2.1.0 § 6.x · MCSB NS-5 · NIST 800-53 SC-5
Evidencia
$ jq '.network.vnets[0] | {name, ddos: .ddos_protection}' < scripts/_security_audit.json
{ "name":"vNet-001","ddos":false }
Impacto. Azure DDoS Protection Standard proporciona mitigación L3 / L4 para las IPs públicas en la VNet protegida. Sin él, los endpoints de cara al público dependen de la protección DDoS Basic gratuita de Azure, que es un pool compartido. Después de que F-02 + F-09 reduzcan la cantidad de IPs públicas a tres (VPN GW + Bastion), DDoS Standard pasa a ser mucho menos importante.
Remediación · Azure CLI
# Generalmente solo vale la pena desplegarlo una vez reducida la superficie de ataque pública
# Y si una carga de trabajo de cara al público requiere mitigación garantizada.
# Costo: US$ 2.944 / mes fijo sin importar cuántas VNet — sugiere diferir hasta que la necesidad del negocio lo justifique.
Validación: tras la remediación, vNet-001.enableDdosProtection = true.
Radio de impacto de la corrección · Bajo · pero el costo es alto en relación con el tamaño de la flota.
F-18Bajo
Higiene de seguridad
La conexión de API azurevm de Logic App está rota (estado Error).
- Recurso
/subscriptions/a1b2c3d4-…/resourceGroups/production-rg/providers/Microsoft.Web/connections/azurevm
Evidencia
$ jq '.web_connections[]' < scripts/_security_extra.json
{ "name":"azurevm","kind":"V1","properties":{ "api":{"displayName":"Azure VM","name":"azurevm"},
"overallStatus":"Error","changedTime":"2026-05-11T23:50:12Z" } }
Impacto. Una conexión de API V1 a "Azure VM" se creó el 2026-05-11 o antes y desde entonces está en estado Error. Las conexiones V1 almacenan credenciales en la capa del conector; incluso cuando están rotas, la referencia a la credencial persiste. Si no se usa, elimínela.
Remediación · Azure CLI
az resource delete --ids /subscriptions/a1b2c3d4-…/resourceGroups/production-rg/providers/Microsoft.Web/connections/azurevm
Validación: az resource list --resource-type Microsoft.Web/connections -g production-rg -o table no devuelve filas.
Radio de impacto de la corrección · Bajo · si hay una Logic App que dependa de ella (ninguna visible actualmente en el inventario), fallará. Verifique primero con Dana.
F-19Bajo / Informativo
Programación de apagado automático de DevTest deshabilitada en ADM-001az.
- Recurso
/subscriptions/a1b2c3d4-…/resourceGroups/PRODUCTION-RG/providers/microsoft.devtestlab/schedules/shutdown-computevm-ADM-001AZ
Evidencia: véase el inventario de § 3.
Impacto. Se configuró un apagado automático para ADM-001az y luego se deshabilitó (y se desactivaron las notificaciones). Operativamente correcto — solo informativo. Se anota porque las programaciones con notificaciones deshabilitadas pueden enmascarar costos / apagados inesperados más adelante si se rehabilitan.
Remediación: ninguna requerida. Si queda en desuso permanente: elimínela.
Radio de impacto de la corrección · Bajo
F-20Informativo
El cifrado de disco es gestionado por la plataforma (sin CMK).
- Recursos
- Los 10 discos administrados (
encryption.type: EncryptionAtRestWithPlatformKey)
- Control
- CIS Azure v2.1.0 § 8.x · MCSB DP-5 · NIST 800-53 SC-12
Evidencia: los bloques de cifrado en todos los discos muestran type: EncryptionAtRestWithPlatformKey. No existen disk encryption sets.
Impacto. Los discos están cifrados con claves gestionadas por Microsoft (predeterminado). Para la mayoría de las cargas de trabajo esto es aceptable — CMK agrega control de rotación y custodia de claves pero genera sobrecarga operativa. Dado que no existe ningún Key Vault (F-12), CMK no es posible actualmente.
Remediación: diferida hasta que se despliegue Key Vault (F-12).
Radio de impacto de la corrección · Medio · el recifrado de disco requiere detener la VM.