摘要

報告分析一個廣泛部署的 ASP.NET 伺服器控制項套件中的四項漏洞鏈,該鏈將未驗證的 AES-CBC padding oracle 轉變成未驗證的遠端程式碼執行:(i) 解密與解析的錯誤 oracle,(ii) 靜態、由密碼衍生的 IV,(iii) 不受限制的 Type.GetType 反序列化 sink,以及 (iv) 透過混合模式組件進行的原生執行(Native execution)。
沒有金鑰也能破解加密?一個混合模式DLL,讓Telerik CBC加密形同虛設! | 資訊安全新聞

1. 架構:RadAsyncUpload 中的加密客戶端狀態

這個控制項套件將每個處理常式與小工具都封裝在單一組件內,並透過單一分派器路由客戶端到伺服器的呼叫,該分派器讀取 type 參數並轉送到已註冊的處理常式。非同步上傳小工具就是其中一個這樣的處理常式。由於它必須在請求之間記住伺服器端設定(允許的副檔名、暫存資料夾、大小限制),它會將該狀態序列化為 JSON,以 AES-CBC 加密,並將密文以隱藏欄位值的形式交給瀏覽器。在下一次 postback 時,伺服器解密相同的 blob 並信任其內容 —— 整個設計建立在一個假設上:沒有金鑰就無法讀取或偽造密文 [1]

2. 解密與解析的 Padding Oracle

CBC 解密會在使用明文之前移除 PKCS#7 padding;如果尾端位元組未編碼成有效的 pad 長度,操作就會失敗。重建上傳設定的處理常式會將解密與 JSON 解析執行為兩個分開、防護方式不同的步驟,以下完全依照已發布的內容呈現:

  1. // Telerik.Web.UI/AsyncUploadHandler.cs (vulnerable revision)
  2. internal IAsyncUploadConfiguration GetConfiguration(string rawData)
  3. {
  4. string[] array = rawData.Split(new char[1] { '&' });
  5. // array[0] = _serializedConfiguration ciphertext (the value we attack)
  6. string obj = array[0];
  7. // array[1] = _serializedConfigurationType ciphertext, decrypted separately
  8. Type type = Type.GetType(CryptoService.GetService().Decrypt(array[1]));
  9. // allowlist check pins the type to one literal string
  10. CryptoService.GetService().CheckWhitelistTypes(type,
  11. ConfigurationManager.AppSettings[
  12. "Telerik.Upload.AllowedCustomMetaDataTypes"
  13. ], "Telerik.Web.UI.AsyncUploadConfiguration");
  14. // decrypt + parse in one call: bad padding -> CryptographicException (wrapped),
  15. // valid padding but garbage JSON -> InvalidOperationException (NOT wrapped)
  16. IAsyncUploadConfiguration asyncUploadConfiguration =
  17. (IAsyncUploadConfiguration)SerializationService.Deserialize(
  18. obj, type, decrypt: true
  19. );
  20. }

解密被一個例外拋出器(Exception thrower)包裝,會將任何加密失敗壓平為單一 CryptographicException ,而接下來的 JavaScriptSerializer 呼叫則未被包裝,因此 padding 正確但格式錯誤的明文會以 InvalidOperationException 浮現。如果系統因為不同原因而拋出不同的錯誤訊息,這種可區分的反應就是 padding oracle 的典型徵兆:翻轉前一個區塊的一個位元組,就能依每次查詢揭露某個猜測的 padding 值是否被接受 [1] 。在一般頁面 postback 上,透過一個客製化 JavaScriptConverter 也存在通往同一個 oracle 的獨立路徑,該轉換器會解密另一個 metaData blob,且在解析步驟上完全沒有例外包裝。

sequenceDiagram participant A as Attacker client participant S as AsyncUploadHandler / Postback converter A->>S: POST ciphertext with byte n of block(i-1) modified S->>S: AES-CBC decrypt block(i) alt PKCS#7 padding invalid S-->>A: CryptographicException else padding valid, plaintext not valid JSON S->>S: JavaScriptSerializer.Deserialize(plaintext) S-->>A: InvalidOperationException end Note over A,S: One query recovers 1 candidate byte Note over A,S: up to 256 queries recover 1 plaintext byte

圖 1 — CVE-2026-13182 背後的解密與解析 oracle。

3. 從 Oracle 到偽造:反向 CBC 與犧牲區塊(The sacrificial block)

Padding oracle 通常是唯讀的:攻擊者若能恢復某區塊的中間密文狀態,就能操控前一個密文區塊,使目標區塊解密成任意明文;但第一個區塊沒有前置區塊,它是與 IV XOR,除非 IV 隨密文傳輸,否則攻擊者無法控制。在這裡,金鑰與 IV 都是在固定 salt 上由單一設定的密碼確定性地衍生:

  1. // Telerik.Web.UI/CryptoService.cs
  2. private static readonly byte[] SALT = new byte[13]
  3. { 58, 84, 91, 25, 10, 34, 29, 68, 60, 88, 44, 51, 1 };
  4. internal static string Decrypt(string encryptedString, string password)
  5. {
  6. byte[] encryptedBytes = Convert.FromBase64String(encryptedString);
  7. Rfc2898DeriveBytes rfc2898DeriveBytes = new Rfc2898DeriveBytes(password, SALT);
  8. // key = first 32 derived bytes, IV = next 16 derived bytes;
  9. // IV is RE-DERIVED on every call, never carried in the ciphertext itself
  10. byte[] bytes = Decrypt(encryptedBytes,
  11. rfc2898DeriveBytes.GetBytes(32), rfc2898DeriveBytes.GetBytes(16));
  12. return Encoding.Unicode.GetString(bytes);
  13. }

固定且不隨密文傳輸的 IV 會阻止攻擊者從零開始構造乾淨的偽造,因為第一個明文區塊無法被完全控制。實務上的繞過是將偽造尾段接在真實密文後面,並利用足夠長的 JSON 字串來吞掉一個不受控制的『sacrificial』垃圾區塊,從而保持語法完整。切割點之後的一切都以反向 CBC 偽造由右至左重建,讓攻擊者能附加任意的 AllowedFileExtensions 值,之後再附加任意的 metadata 物件,同時訊息仍能解析為有效 JSON [1]

4. 型別混淆:從偽造 metadata 到 .NET Gadget

由於 metadata blob 可以在沒有金鑰的情況下被偽造,攻擊者完全控制用來重建已上傳檔案結果物件的類別名稱與屬性值,而該物件會透過一個完全沒有 allowlist 的 getter 讀回:

  1. // Telerik.Web.UI/AsyncUploadHandler+FileUploadedEventArgs (UploadResult getter)
  2. public IAsyncUploadResult UploadResult
  3. {
  4. get
  5. {
  6. AsyncUploadedFile obj = File as AsyncUploadedFile;
  7. // obj.FileType and obj.SerializedData both originate from the
  8. // attacker-forged metaData blob decrypted earlier — no type check here
  9. return (IAsyncUploadResult)SerializationService.Deserialize(
  10. type: Type.GetType(obj.FileType),
  11. obj: obj.SerializedData);
  12. }
  13. }

這就是被追蹤為 CVE-2026-13181 的不受限制反序列化 sink。將解析出的型別設為 System.Configuration.Install.AssemblyInstaller ,並將其由 JSON 填入的 Path 屬性設為某個伺服器路徑,會讓屬性 setter 對該路徑呼叫 Assembly.LoadFrom —— 這是此類 sink shape 中已有充分文獻記載的 gadget [1] 。這個 sink 只有在裝載應用程式自己的處理常式於伺服器端讀取 e.UploadResult 時才會觸發,而大多數真實的上傳處理常式都會這麼做。

5. 透過混合模式組件(Assembly)執行原生程式碼

載入組件與執行組件並不相同;一個單純的受控 DLL 在被叫用方法之前都靜置不動。這個 payload 改以 C++/CLI「混合模式」 image 發佈,同時是合法的 .NET 組件,也是具有真正 DllMain 的原生 Windows PE。載入器會在 Assembly.LoadFrom 映射該映像的當下、任何 managed code 執行之前就叫用 DllMain ,因此原生邏輯會作為載入的副作用而執行。它在 loader lock 下執行,避開檔案系統查找,改為讀取工作程式 process 自己的命令列 —— 已安全映射於記憶體中 —— 以定位其設定檔,並從中找出 web root,然後投放一個自解密 shell 頁面,或在唯讀部署上僅在記憶體中修補即時請求 pipeline [1]

6. 規避修補層級的緩解措施

較後期的版本透過將整個解密與解析呼叫包在單一 catch 區塊中,部分關閉了處理常式端的 oracle:

  1. // Telerik.Web.UI/AsyncUploadHandler.cs (patched: 2026.1.421)
  2. internal IAsyncUploadConfiguration GetConfiguration(string rawData)
  3. {
  4. try
  5. {
  6. // identical decrypt-then-parse logic as before
  7. var config = (IAsyncUploadConfiguration)
  8. SerializationService.Deserialize(serializedConfig, type, true);
  9. return config;
  10. }
  11. catch (Exception)
  12. {
  13. // both CryptographicException and InvalidOperationException now
  14. // collapse into a single, indistinguishable response
  15. throw new CryptographicException(
  16. "The cryptographic operation has failed!");
  17. }
  18. }

這個修正只觸及處理常式路徑;獨立的 postback 路徑仍繼續傳播兩種不同的例外,使 oracle 仍可使用。同一版本新增了兩道護欄 —— 一個 CSRF token 與一個綁定 session 的上傳識別碼 —— 但兩者都被拼接技巧本身破解,因為第 3 節中重用的密文前綴是幾秒前從即時頁面繪製中擷取的,因此依構造本身就帶有真正有效的 token,而非偽造的:

  1. // Telerik.Web.UI.AsyncUpload/AsyncUploadConfiguration.cs
  2. public class AsyncUploadConfiguration
  3. {
  4. public string TargetFolder { get; set; }
  5. public string TempTargetFolder { get; set; }
  6. public int MaxFileSize { get; set; }
  7. public TimeSpan TimeToLive { get; set; }
  8. public string CsrfToken { get; set; } // sits inside the REUSED prefix
  9. public bool UseApplicationPoolImpersonation { get; set; }
  10. public string[] AllowedFileExtensions { get; set; } // this is the FORGED suffix
  11. }

由於宣告順序決定序列化順序,CsrfToken 總是排在 AllowedFileExtensions 之前,並隨合法前綴保持完整;唯有最後的欄位,以及分隔它的犧牲區塊,會被偽造。

sequenceDiagram participant C as Exploit client participant P as RadAsyncUpload page (postback oracle) participant H as WebResource.axd handler participant W as Worker process C->>P: CsrfToken + _pageGUID loop backwards-CBC forging via oracle C->>P: probe crafted ciphertext block P-->>C: padding valid / invalid end C->>C: splice forged AllowedFileExtensions onto genuine prefix C->>H: upload mixed-mode DLL (chunked) H-->>C: encrypted MetaData blob C->>P: oracle-decrypt MetaData block 0 (recover IV, TempTargetFolder) C->>H: POST forged rau_ClientState (AsyncUploadTypeName = AssemblyInstaller) H->>W: Type.GetType + Deserialize -> Assembly.LoadFrom(DLL) W->>W: DllMain fires; native payload writes/activates shell C->>W: request shell, run command W-->>C: command output

圖 2 — 依據對存活的 postback oracle 所發布的漏洞利用執行重建的端到端鏈 [1]

7. 時間通道韌性

即使完全正規化的錯誤訊息也不等於執行時間相等:拒絕錯誤 padding 會提早返回,而 padding 通過驗證但 JSON 解析失敗則在失敗前多做了更多的工作。這個差距在訊息正規化後依然存在,並可使用 Timeless Timing Attack(TTA) 在廣域網路上測量,該攻擊將兩個請求競速於單一封包內,因此攜帶訊號的是相對到達順序 —— 而非絕對延遲 —— 藉此避開網路抖動 [2] (Network jitter)。這說明,隱藏 oracle 的文字輸出雖然必要,但若兩條程式路徑在執行代價上確實不同,仍不足以防止攻擊。

8. 修復

廠商最終的修正以 AES-GCM 取代 AES-CBC 來處理這些 blob。驗證式加密會附加一個在任何解密發生之前先驗證的完整性標籤,因此單一被竄改的位元組會一致且立即被拒絕 —— 沒有 padding 步驟可供探測,沒有解密與解析的區分可供依內容或時間辨別。攻擊鏈中每一個後續階段 —— 偽造、型別混淆、原生執行 —— 都取決於伺服器先接受攻擊者修改過的密文,而驗證式加密會從源頭拒絕這點 [1]

9. 相關工作

第 4 節中的型別混淆步驟屬於更廣泛的一類,已在 不安全反序列化模式 的一般論述中被記錄,該論述強調反序列化期間未檢查的物件型別解析,正是將攻擊者控制的輸入變成任意物件建構的關鍵,與執行環境無關。一個可比較的案例是 模型服務框架中的未驗證反序列化 RCE ,其中一個未過濾的反序列化呼叫同樣在一旦到達後就足以造成程式碼執行 —— 再次強化決定性因素是反序列化邊界上缺乏型別 allowlist。

10. 結論

這條攻擊鏈中的每個缺陷單獨來看都不稀奇:CBC oracle 缺乏訊息驗證、決定性的 IV、未檢查的 type-resolution sink,以及載入器副作用,這些問題各自都早已為人所知。其重要性在於組合性 —— 每個弱連結都恰好提供下一步所需的 primitive,從位元組層級的明文還原到完整的密文偽造,從偽造的 metadata 到任意型別具現化,再到原生執行,而攻擊者從未持有加密金鑰。