mxcheck es una herramienta de escaneo de información y seguridad diseñada para evaluar servidores de correo electrónico
mxcheck es una herramienta de escaneo de información y seguridad diseñada para evaluar servidores de correo electrónico
mxcheck es una herramienta de escaneo de información y seguridad diseñada específicamente para evaluar servidores de correo electrónico. Fue añadida a los repositorios de Kali Linux (a partir de la versión 2024.3) y está escrita en Go, lo que la hace muy rápida y eficiente para auditorías.
Su objetivo principal es ayudar a los pentesters o administradores de sistemas a descubrir malas configuraciones que podrían facilitar campañas de phishing, suplantación de identidad (spoofing) o envío de spam.
¿Qué analiza mxcheck?
Cuando ejecutas la herramienta contra un dominio, realiza de forma automatizada las siguientes verificaciones:
- Registros DNS de correo: Verifica la existencia y configuración de registros clave de seguridad y enrutamiento (A, MX, PTR, SPF, MTA-STS, DKIM y DMARC).
- Fugas de Información: Comprueba si el servidor revela datos a través de su banner de bienvenida o mediante comandos SMTP como
VRFY(que permite a un atacante enumerar usuarios válidos). - Open Relay: Comprueba si el servidor permite enviar correos a cualquier destino sin autenticación (una vulnerabilidad clásica aprovechada para enviar spam).
- Cifrado (StartTLS): Valida si el servidor soporta StartTLS y analiza su certificado SSL/TLS.
- Escaneo de Puertos: Busca si los puertos estándar de correo (25, 465, 587) están abiertos.
- Listas Negras (Blacklists): Revisa si la IP del servidor de correo ha sido reportada por enviar spam.
- SMTP Smuggling: Evalúa si el servidor es vulnerable al contrabando SMTP, una técnica de evasión que permite falsificar remitentes burlando protecciones como SPF.
- Geolocalización y Red: Identifica el número de Sistema Autónomo (ASN) y el país donde se aloja el servidor.
Cómo instalarlo en Kali Linux
Si utilizas una versión reciente de Kali, es probable que ya se encuentre en los repositorios oficiales. Puedes instalarla desde la terminal:
Bash
sudo apt update
sudo apt install mxcheck
Uso básico y ejemplos
La sintaxis fundamental consiste en usar la opción
-s (service) seguida del dominio del servidor que deseas analizar. Escaneo rápido a un dominio:
Bash
mxcheck -s ejemplo.com
Escaneo completo (con listas negras y prueba de SMTP Smuggling):
Si quieres una auditoría profunda que incluya revisión de blacklists (
-b) y un test de vulnerabilidad Smuggling (-g):Bash
mxcheck -s ejemplo.com -b -g
Escaneo sin interactividad exportando los resultados:
Ideal para automatizaciones en pentesting. Anula las preguntas en pantalla (
-n) y guarda la salida en un archivo con formato TSV (-w):Bash
mxcheck -s ejemplo.com -n -w reporte_ejemplo.tsv
Opciones más comunes (Flags)
| Flag | Descripción |
-s, --service | Especifica el dominio o host objetivo. |
-b, --blacklist | Habilita la consulta en listas negras de spam. |
-g, --smuggle | Activa la prueba para la vulnerabilidad SMTP Smuggling. |
-S, --dkim-selector | Verifica el registro DKIM (requiere que indiques el selector, por ejemplo default). |
-p, --disable-port-scan | Deshabilita el escaneo de puertos (útil para pruebas más sigilosas o rápidas). |
-d, --dnsserver | Especifica un servidor DNS personalizado (por defecto usa 8.8.8.8). |
La mitigación definitiva para el SMTP Smuggling a nivel de administración de sistemas y Blue Teaming consiste en forzar a los servidores a cumplir estrictamente con el RFC 5321. Esto significa configurar los demonios SMTP para que rechacen o normalicen cualquier secuencia de fin de datos que no sea exactamente
<CR><LF>.<CR><LF>.Dado que las configuraciones permisivas son la raíz del problema, aquí tienes las estrategias de endurecimiento (hardening) para las plataformas más comunes en entornos de producción.
Mitigación en Postfix (Entornos Linux)
Wietse Venema parcheó Postfix a finales de diciembre de 2023. Las versiones actualizadas introdujeron parámetros específicos para manejar los saltos de línea desnudos (
bare newlines).Para parchear e implementar la política estricta en la infraestructura:
Mitigación en Microsoft Exchange
El enfoque para Microsoft Exchange varía dependiendo de si la infraestructura es On-Premises o en la nube.
Exchange Online (Microsoft 365):
Microsoft ya ha implementado los parches necesarios en su infraestructura en la nube. Los servidores de Exchange Online ahora descartan automáticamente los correos que intentan explotar discrepancias en la secuencia de fin de datos. No se requiere acción manual por parte del administrador.
Exchange Server (On-Premises):
Para los servidores alojados localmente, la mitigación requiere aplicar las actualizaciones de seguridad más recientes proporcionadas por Microsoft, ya que el comportamiento del parser SMTP está incrustado en el código base del servicio de transporte.
- Instalar el último CU/SU: Debes aplicar el Cumulative Update (CU) más reciente compatible con tu versión de Exchange y, sobre este, instalar el Security Update (SU) correspondiente (marzo de 2024 en adelante).
- Verificación de enrutamiento: Asegúrate de que cualquier dispositivo intermedio (como balanceadores de carga proxy o firewalls de aplicaciones web, como Nginx o F5, si inspeccionan tráfico SMTP) no esté modificando o despojando los caracteres
<CR>antes de que el tráfico llegue al servidor Exchange.
Auditoría y Comprobación
Una vez aplicados los parches, es crucial auditar la infraestructura para garantizar que la vulnerabilidad haya sido erradicada.
Puedes realizar una prueba manual utilizando
netcat o telnet simulando el ataque:Bash
nc servidor_correo.com 25
EHLO midominio.com
MAIL FROM:<test@midominio.com>
RCPT TO:<victima@objetivo.com>
DATA
Subject: Prueba de Smuggling
Este es el primer mensaje.
<LF>.<LF>
MAIL FROM:<spoof@objetivo.com>
RCPT TO:<victima@objetivo.com>
DATA
Subject: Mensaje contrabandeado
Este mensaje no debería pasar.
.
QUIT
(Nota: debes presionar
Enter en Linux para generar un <LF> estándar, o usar un script en Python/Bash que envíe los bytes exactos \n.\n).Si el servidor está correctamente fortificado, rechazará la secuencia intermedia o la tratará como parte del cuerpo del primer mensaje, en lugar de enrutar el segundo correo.
Para automatizar esta auditoría a gran escala, la mejor aproximación es utilizar Python. Las bibliotecas estándar como
smtplib no nos sirven para este caso específico, ya que automáticamente "corrigen" y estandarizan los saltos de línea a <CR><LF>, impidiendo que enviemos la secuencia malformada necesaria para la prueba.La solución es interactuar directamente a bajo nivel utilizando la biblioteca
socket de Python para inyectar los bytes exactos (\n.\n).Aquí tienes una herramienta de automatización diseñada para flujos de trabajo de pentesting. Soporta la evaluación concurrente de rangos de red (CIDR) y listas de IPs.
Script en Python: smtp_smuggle_scanner.py
Este script intenta iniciar una sesión SMTP y enviar un cuerpo de mensaje finalizado únicamente con saltos de línea desnudos (
<LF>.<LF>).Python
#!/usr/bin/env python3
import socket
import argparse
import ipaddress
from concurrent.futures import ThreadPoolExecutor
def audit_smuggling(target_ip, port, timeout):
"""
Se conecta al servidor SMTP y envía una secuencia de fin de datos
malformada (bare LFs) para comprobar si el servidor es permisivo.
"""
try:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.settimeout(timeout)
s.connect((target_ip, port))
# Leer el banner inicial
s.recv(1024)
# Iniciar sesión SMTP
s.sendall(b"EHLO audit.local\r\n")
s.recv(1024)
s.sendall(b"MAIL FROM:<auditor@audit.local>\r\n")
s.recv(1024)
s.sendall(f"RCPT TO:<admin@{target_ip}>\r\n".encode())
s.recv(1024)
s.sendall(b"DATA\r\n")
s.recv(1024)
# INYECCIÓN DEL PAYLOAD (El final de datos es \n.\n en lugar de \r\n.\r\n)
payload = b"Subject: Auditoria SMTP Smuggling\r\n\r\nCuerpo del mensaje de prueba.\n.\n"
s.sendall(payload)
# Leer cómo responde el servidor a la secuencia malformada
response = s.recv(1024).decode('utf-8', errors='ignore').strip()
s.sendall(b"QUIT\r\n")
s.close()
# Analizar la respuesta
if response.startswith("250"):
return f"[!] VULNERABLE / PERMISIVO: {target_ip}:{port} aceptó el mensaje (Respuesta: {response})"
elif "bare newline" in response.lower() or response.startswith("552") or response.startswith("500"):
return f"[+] SEGURO: {target_ip}:{port} rechazó la secuencia (Respuesta: {response})"
else:
return f"[*] INCIERTO: {target_ip}:{port} comportamiento no estándar (Respuesta: {response})"
except socket.timeout:
return f"[-] TIMEOUT: {target_ip}:{port} no respondió a tiempo."
except ConnectionRefusedError:
return f"[-] CERRADO: {target_ip}:{port} rechazó la conexión."
except Exception as e:
return f"[-] ERROR: {target_ip}:{port} - {type(e).__name__}"
def main():
parser = argparse.ArgumentParser(description="Escáner concurrente para SMTP Smuggling (Bare LF)")
parser.add_argument("-t", "--target", help="IP objetivo o rango CIDR (ej. 192.168.1.0/24)", required=True)
parser.add_argument("-p", "--port", type=int, default=25, help="Puerto SMTP (por defecto: 25)")
parser.add_argument("--timeout", type=int, default=5, help="Timeout de conexión en segundos (por defecto: 5)")
parser.add_argument("-w", "--workers", type=int, default=10, help="Número de hilos concurrentes (por defecto: 10)")
args = parser.args()
# Generar la lista de IPs a partir del CIDR o IP individual
try:
if '/' in args.target:
targets = [str(ip) for ip in ipaddress.IPv4Network(args.target, strict=False)]
else:
targets = [args.target]
except ValueError as e:
print(f"Error parseando el objetivo: {e}")
return
print(f"[*] Iniciando auditoría SMTP Smuggling contra {len(targets)} objetivo(s)...")
print(f"[*] Hilos: {args.workers} | Puerto: {args.port}\n")
# Ejecución concurrente
with ThreadPoolExecutor(max_workers=args.workers) as executor:
futures = {executor.submit(audit_smuggling, ip, args.port, args.timeout): ip for ip in targets}
for future in futures:
result = future.result()
if not result.startswith("[-] CERRADO") and not result.startswith("[-] TIMEOUT"):
# Filtramos los puertos cerrados para limpiar la salida en escaneos de subredes grandes
print(result)
if __name__ == "__main__":
main()
Cómo utilizar el script
Guárdalo como
smtp_smuggle_scanner.py y otórgale permisos de ejecución:Bash
chmod +x smtp_smuggle_scanner.py
Escaneo de un solo objetivo:
Bash
./smtp_smuggle_scanner.py -t 10.10.50.15
Auditoría de una subred entera con 20 hilos simultáneos:
Esta es la función más útil durante la fase de descubrimiento de infraestructura externa o evaluación de redes internas.
Bash
./smtp_smuggle_scanner.py -t 10.10.50.0/24 -w 20
Nota operativa: Para no generar ruido excesivo en los logs de la consola, el script está configurado para omitir silenciosamente las IPs donde el puerto 25 está cerrado (Connection Refused) o no responde (Timeout), mostrando únicamente los servidores SMTP que devuelven un banner válido.
Para integrar la exportación de resultados a formatos profesionales como JSON (ideal para automatizaciones o ingestionar en plataformas de gestión de vulnerabilidades) y CSV (perfecto para hojas de cálculo en informes ejecutivos), podemos utilizar las bibliotecas nativas
json y csv de Python.El script actualizado incorpora dos opciones nuevas mediante argumentos de consola:
--json y --csv, las cuales guardarán únicamente los hallazgos relevantes (servidores vulnerables o con comportamientos inciertos) estructurados de manera limpia.Script Completo Actualizado: smtp_smuggle_report.py
Python
#!/usr/bin/env python3
import socket
import argparse
import ipaddress
import json
import csv
import xml.etree.ElementTree as ET
from concurrent.futures import ThreadPoolExecutor
def parse_nmap_xml(xml_file):
"""
Parsea el archivo XML de Nmap y devuelve una lista de tuplas (IP, Puerto)
solo para los hosts activos y puertos SMTP abiertos.
"""
targets = []
puertos_smtp = {25, 465, 587, 2525}
try:
tree = ET.parse(xml_file)
root = tree.getroot()
for host in root.findall('host'):
status = host.find('status')
if status is None or status.get('state') != 'up':
continue
ip = None
for address in host.findall('address'):
if address.get('addrtype') == 'ipv4':
ip = address.get('addr')
break
if not ip:
continue
ports = host.find('ports')
if ports is not None:
for port in ports.findall('port'):
state = port.find('state')
if state is not None and state.get('state') == 'open':
port_id = int(port.get('portid'))
if port_id in puertos_smtp:
targets.append((ip, port_id))
except Exception as e:
print(f"[-] Error al procesar el archivo XML: {e}")
return targets
def audit_smuggling(target_ip, port, timeout):
"""
Intenta inyectar la secuencia de saltos de línea desnudos (bare LFs).
Retorna un diccionario estructurado con los detalles del hallazgo.
"""
resultado = {
"ip": target_ip,
"port": port,
"status": "ERROR",
"description": "",
"response": ""
}
try:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.settimeout(timeout)
s.connect((target_ip, port))
s.recv(1024)
s.sendall(b"EHLO audit.local\r\n")
s.recv(1024)
s.sendall(b"MAIL FROM:<auditor@audit.local>\r\n")
s.recv(1024)
s.sendall(f"RCPT TO:<admin@{target_ip}>\r\n".encode())
s.recv(1024)
s.sendall(b"DATA\r\n")
s.recv(1024)
payload = b"Subject: Auditoria SMTP Smuggling\r\n\r\nCuerpo del mensaje.\n.\n"
s.sendall(payload)
response = s.recv(1024).decode('utf-8', errors='ignore').strip()
s.sendall(b"QUIT\r\n")
s.close()
resultado["response"] = response
if response.startswith("250"):
resultado["status"] = "VULNERABLE"
resultado["description"] = f"[!] VULNERABLE: {target_ip}:{port} aceptó el mensaje."
elif "bare newline" in response.lower() or response.startswith("552") or response.startswith("500"):
resultado["status"] = "SEGURO"
resultado["description"] = f"[+] SEGURO: {target_ip}:{port} rechazó la secuencia."
else:
resultado["status"] = "INCIERTO"
resultado["description"] = f"[*] INCIERTO: {target_ip}:{port} comportamiento no estándar."
except socket.timeout:
resultado["status"] = "TIMEOUT"
resultado["description"] = f"[-] TIMEOUT: {target_ip}:{port} no respondió a tiempo."
except ConnectionRefusedError:
resultado["status"] = "CERRADO"
resultado["description"] = f"[-] CERRADO: {target_ip}:{port} rechazó la conexión."
except Exception as e:
resultado["status"] = "ERROR"
resultado["description"] = f"[-] ERROR: {target_ip}:{port} - {type(e).__name__}"
return resultado
def export_results(results, json_file, csv_file):
"""
Exporta los resultados recolectados a archivos JSON o CSV.
"""
# Filtrar solo los resultados relevantes o guardar todos según preferencia.
# Aquí guardaremos todos los que tengan estado distinto de CERRADO y TIMEOUT para limpiar el reporte.
filtered_results = [r for r in results if r["status"] not in ["CERRADO", "TIMEOUT", "ERROR"]]
if json_file:
try:
with open(json_file, 'w', encoding='utf-8') as f:
json.dump(filtered_results, f, indent=4, ensure_ascii=False)
print(f"\n[+] Resultados exportados exitosamente a JSON: {json_file}")
except Exception as e:
print(f"[-] Error al exportar JSON: {e}")
if csv_file:
try:
with open(csv_file, 'w', newline='', encoding='utf-8') as f:
writer = csv.writer(f)
# Escribir cabeceras
writer.writerow(["IP", "Puerto", "Estado", "Respuesta SMTP", "Descripción"])
# Escribir filas
for r in filtered_results:
writer.writerow([r["ip"], r["port"], r["status"], r["response"], r["description"]])
print(f"\n[+] Resultados exportados exitosamente a CSV: {csv_file}")
except Exception as e:
print(f"[-] Error al exportar CSV: {e}")
def main():
parser = argparse.ArgumentParser(description="Escáner SMTP Smuggling con opciones de exportación para reportes")
group = parser.add_mutually_exclusive_group(required=True)
group.add_argument("-t", "--target", help="IP objetivo o rango CIDR")
group.add_argument("-x", "--xml", help="Ruta al archivo XML generado por Nmap")
parser.add_argument("-p", "--port", type=int, default=25, help="Puerto por defecto si se usa -t")
parser.add_argument("--timeout", type=int, default=5, help="Timeout en segundos")
parser.add_argument("-w", "--workers", type=int, default=10, help="Hilos concurrentes")
# Nuevos argumentos para reportes
parser.add_argument("--json", help="Ruta del archivo de salida en formato JSON")
parser.add_argument("--csv", help="Ruta del archivo de salida en formato CSV")
args = parser.parse_args()
targets = []
if args.xml:
targets = parse_nmap_xml(args.xml)
print(f"[*] Extraídos {len(targets)} servicios SMTP abiertos del XML.")
else:
try:
if '/' in args.target:
ips = [str(ip) for ip in ipaddress.IPv4Network(args.target, strict=False)]
else:
ips = [args.target]
targets = [(ip, args.port) for ip in ips]
except ValueError as e:
print(f"[-] Error parseando el objetivo: {e}")
return
if not targets:
print("[-] No hay objetivos válidos para escanear. Saliendo.")
return
print("[*] Iniciando auditoría...")
all_results = []
with ThreadPoolExecutor(max_workers=args.workers) as executor:
futures = {executor.submit(audit_smuggling, ip, port, args.timeout): (ip, port) for ip, port in targets}
for future in futures:
res = future.result()
all_results.append(res)
# Mostrar en consola solo si no está cerrado
if res["status"] not in ["CERRADO", "TIMEOUT"]:
print(res["description"])
# Ejecutar la exportación si se especificaron los flags correspondientes
if args.json or args.csv:
export_results(all_results, args.json, args.csv)
if __name__ == "__main__":
main()
Ejemplos de Uso con los Nuevos Reportes
1. Exportar los resultados directamente a un archivo CSV para adjuntar a la hoja de cálculo del informe:
Bash
./smtp_smuggle_report.py -x escaneo_correo.xml --csv reporte_auditoria.csv -w 15
2. Exportar en formato JSON estructurado para procesos automatizados:
Bash
./smtp_smuggle_report.py -t 192.168.1.0/24 --json resultados.json -w 20
3. Generar ambos formatos de forma simultánea:
Bash
./smtp_smuggle_report.py -x escaneo_correo.xml --json hallazgos.json --csv hallazgos.csv
¿Qué se incluye en la exportación?
- Filtro inteligente de ruido: El script descarta automáticamente los puertos cerrados o timeouts al momento de escribir el archivo final, asegurando que tu informe CSV o JSON contenga únicamente datos limpios y procesables (servidores vulnerables, seguros o con comportamientos inciertos).
- Trazabilidad: Tanto el CSV como el JSON registran la dirección IP, el puerto analizado, el veredicto del estado de seguridad y la respuesta exacta enviada por el servidor SMTP (
response), lo cual sirve como evidencia técnica de respaldo para el cliente o equipo de infraestructura.