Automated Threat Modeling

Brief Summary: Control Web Panel CVE-2026-77878, Root OS Command Injection via the restoredatabase API

A brief summary of CVE-2026-77878, an OS command injection in Control Web Panel's loopback database restore API that runs as root, including patch and detection guidance.

CVE Analysis

11 min read

Gaurav Sarraf
Gaurav Sarraf

2026-11-03

Brief Summary: Control Web Panel CVE-2026-77878, Root OS Command Injection via the restoredatabase API
Experimental AI-Generated Content

This CVE analysis is an experimental publication that is completely AI-generated. The content may contain errors or inaccuracies and is subject to change as more information becomes available. We are continuously refining our process.

If you have feedback, questions, or notice any errors, please reach out to us.

[email protected]

Introduction

A root-owned helper service listening on localhost is the kind of component defenders tend to overlook. In Control Web Panel, that service accepted a database backup path and dropped it straight into a shell command running as root. CVE-2026-77878 turns that oversight into a local-to-root escalation: any hosting account that can reach the loopback API can run arbitrary commands as uid 0.

Control Web Panel (CWP), formerly CentOS Web Panel, is a Linux hosting and server management control panel used by hosting providers and system administrators on dedicated servers and VPS environments. It bundles email administration, DNS management, file management, and system monitoring into Free and PRO offerings. CWP sits at the privileged center of multi-tenant hosting servers, so a flaw that crosses from a tenant process to root affects everyone on the box.

Technical Information

The vulnerable functionality is the loopback API route /v1/restoredatabase/ on CWP's root-owned user API, bound to 127.0.0.1:2302. This service runs as root and talks to MySQL as root@localhost. VulnCheck decoded the ionCube-encoded build and recovered the sink: the route reads a POST field named file and passes it to BackupHandler::restoreDBBackup(), which concatenates the value, without escaping, into a shell command executed as root.

The recovered sink is:

shell_exec('mysql --defaults-extra-file=/root/.my.cnf < ' . $file . ' > /dev/null 2>&1 &');

Three conditions combined to make this exploitable. First, the route did not check a token, so it was reachable without authentication (tracked separately as CVE-2026-77877). Second, the file path was not restricted to any location, so the caller could point root's mysql client at any file on the system. Third, the value was never quoted, so it became shell syntax rather than remaining a filename. Any shell metacharacter in file, such as ;, |, &, a backtick, or $(, therefore introduced additional commands that ran as uid 0. This is OS command injection, classified as CWE-78, not merely a malicious SQL statement inside a restored database.

Attack flow

In VulnCheck's reproduction, the chain proceeds in stages:

  1. The attacker first obtains code execution as a hosting user, for example through an older pre-auth module dispatch path on the user panel (port 2083).
  2. From that tenant context, the attacker sends a loopback request to the restore route, which in the vulnerable implementation lacks a token check.
  3. A crafted file value injects additional shell commands, which execute as root.
  4. The resulting proof file is root owned and contains uid=0(root).

The published escalation request looks like this:

curl -sk https://127.0.0.1:2302/v1/restoredatabase/ \ --data-urlencode "file=/dev/null ; id > /tmp/proof ; chmod 666 /tmp/proof ; echo"

The critical boundary here is between an unprivileged hosting context and a privileged local helper. The local attack classification (AV:L) does not reduce the demonstrated root impact. A companion flaw, CVE-2026-77877, separately describes missing authentication on root privileged loopback API routes, explicitly including /v1/restoredatabase/. Missing authentication and unsafe shell construction are different weaknesses that happen to expose the same privileged service.

It helps to be precise about what is and is not part of this CVE. The broader CWP2Shell research describes an unauthenticated login flow that issues a redirect but continues execution because it lacks an exit(), letting the dispatcher load an attacker selected module. In the historical chain, this supplies hosting account execution, which can then reach the loopback API; the article notes that builds before 1.12 permitted the unauthenticated path to root from a single valid account name. That remote chain is distinct from CVE-2026-77878. The standalone CVE vectors are:

  • CVSS 4.0: CVSS:4.0/AV:L/AC:L/AT:N/PR:N/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N (8.6 High)
  • CVSS 3.1: CVSS:3.1/AV:L/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H (8.4 High)

Patch Information

CWP is a closed-source, ionCube-encoded product, so there is no public commit or diff for this bug. The fix ships only in vendor releases. VulnCheck decoded the patched build and confirmed it: the bug was fixed in CWP 1.29 (released 31 Oct 2026), and its own testing of CWP 1.30 found all ten CWP2Shell CVEs fixed with no regressions. The sources disagree on where the cutoff sits. NVD lists the affected range as < 1.29, but the VulnCheck CNA advisory still says < 1.30. The safe baseline is 1.30 or later. The current LTS release is 1.30.1.

Fixing the underlying bug took four layers, per VulnCheck's analysis of the decoded code:

  1. Authentication before anything runs. The route now calls the API's access_token() gate before it even loads the backup handler. A request without a valid token gets a 403 "Token Invalid". This closes the route's unauthenticated access, tracked separately as CVE-2026-77877.
  2. Scoping to the calling account. BackupHandler now runs for the account making the request instead of acting for the whole server, so one hosting user cannot restore through another user's context.
  3. Path confinement. restoreDBBackup() resolves the supplied path with realpath and rejects anything outside /home/<user>/backupcwp/mysql. Because realpath resolves ../ sequences and symlinks first, the containment check cannot be bypassed with traversal tricks.
  4. Shell-argument escaping. The path is wrapped in escapeshellarg before it reaches the shell, so characters like ; or | are treated as part of a filename, not as command separators. This is the direct fix for the CWE-78 injection; the other three layers limit what an authenticated caller can do even when the input is clean.

VulnCheck notes that backup_handler.php changed in this release for the first time since 2022. In the 1.16 and 1.23 builds, released after other CVEs in the chain were fixed, the file was unchanged and the route was still exploitable.

The 2302 API still has no global auth middleware. Instead, 1.27 and 1.29 added shared helpers that each sensitive route calls:

  • cwp_clean_username(): allow-list ^[A-Za-z0-9._-]+$, also rejects ..
  • cwp_api_safe_home_path(): realpath containment under /home/<user>
  • cwp_api_as_user(): runs commands as the account through runuser -u <user> with escapeshellarg, not as root

The vendor's 1.29 notes do not name the CVE or reporter, but describe the same change: several internal user-panel API actions, including "backup start and database restore", now require a valid session, and database restores run under a restricted restore account that can only reach the account's own databases. The notes claim "unused legacy restore actions were removed", but VulnCheck found the restore* routes still registered, now authenticated and hardened. Version 1.30.1 goes further, limiting the user-panel internal API's account details and account management to the account's own panel and to the system itself.

Release timeline for this CVE:

  • 1.16 and 1.23: no change; the root shell sink was still reachable without a token.
  • 1.27: the MySQL Manager on port 2302 stopped passing user input through a root shell.
  • 1.29: session requirement and account-scoped, path-confined, escaped restores. CVE-2026-77878 fixed.
  • 1.30: VulnCheck confirmed the fix in the decoded code.
  • 1.30.1: further hardening of the internal API.

Detection Methods

CVE-2026-77878 lives in CWP's user API, a small service bound to 127.0.0.1:2302 that runs as root. The record scores the CVE as local (AV:L/PR:L), and in practice an attacker first gets code running as a hosting user, then sends a loopback request to /v1/restoredatabase/ and gets root. The malicious request never leaves the host and goes over HTTPS on loopback, so a network IDS or perimeter WAF will not see it. You need host telemetry: CWP's own web server logs, process creation events, and file artifacts. None of the reviewed sources includes a ready-made Sigma, YARA, or Suricata rule, so you will need to write your own from the behaviour below.

1. Know the sink so you can recognise abuse of it. The route reads a POST field named file and concatenates it into a root shell command. Normally file is just the path to a SQL dump. On fixed builds (1.29+), restores are limited to /home/<user>/backupcwp/mysql (checked with realpath). So a file value that contains shell metacharacters (;, |, &, backtick, $() or points anywhere outside that directory is suspicious. Look for POST requests to /v1/restoredatabase/ that contain shell metacharacters or unusual file paths.

2. Process-tree hunting (highest fidelity). Every exploit attempt runs through the root shell that shell_exec spawns, so EDR or auditd execve logging is the most reliable place to catch it. Look for:

  • A root sh -c whose command line starts with mysql --defaults-extra-file=/root/.my.cnf < and then has extra commands chained after the path. Legitimate restores contain one redirect to a dump file and nothing else.
  • Children of the user-API PHP worker or its shell that are anything other than mysql, running as uid 0: id, chmod, curl, bash, nc, reverse-shell interpreters, and so on.

3. Catch the hop from hosting user to loopback. The published escalation request gives a strong behavioural signal: a non-root process, especially one whose parent is a tenant's PHP-FPM pool or web process, opening a connection to 127.0.0.1:2302. Hosting tenants have no reason to call the root user API directly with curl. Watch for curl or wget command lines that include 127.0.0.1:2302, /v1/restoredatabase/, or --data-urlencode "file=.

4. Web server log review. All panels sit behind a single custom nginx that maps ports to PHP-FPM pools, so check its logs for requests to the 2302 listener at /v1/restoredatabase/. Standard access logs do not record POST bodies, so the full payload usually will not be there. What you can still use is request frequency, timing that lines up with suspicious activity on the 2083 user panel, and status codes. On patched builds, the route answers an untokened request with 403 "Token Invalid" before the handler loads, so a burst of 403s on this route after you patch points to someone probing it.

5. Chain-context indicators. The CVE is almost always the second stage, so hunt for the first stage alongside it:

  • POSTs to /<account>/index.php?module=phpini_editor&acc=addlabelphpiniadv or acc=update, or module=protected_directory&acc=deletedp, on port 2083. Unauthenticated requests here get a 302 to /login/ even though the module code has already run.
  • module= values containing ../ traversal sequences.
  • PHP files dropped in world-writable paths. VulnCheck's exploit planted a hidden, randomly named webshell such as /tmp/.yDKSWndF.php.
  • Account enumeration: a sweep of GET /<name>/ requests where 302 means the account exists and 404 means it does not.

6. File artifacts from public PoCs. The researcher's proof payload creates /tmp/proof, owned by root, mode 666, containing uid=0(root). Real attackers will change the file name, but a root-owned, world-writable file in /tmp holding id output is worth a file-integrity alert. Be careful: the user-panel stage of the same chain writes the same /tmp/proof with the hosting user's uid. Check the file's owner and contents to tell which stage produced it.

7. Exposure check. The quickest way to find exposed hosts is to read each server's CWP webpanel version. The sources disagree on the cutoff: the CVE title says "< 1.30", while the CNA description says "before webpanel_version 1.29". VulnCheck's decoded-code analysis shows the fix landed in 1.29. Any host below that should be treated as exploitable by any local hosting account and gets the highest priority for the process and file hunting above.

Affected Systems and Versions

The affected product is Control Web Panel (CWP), formerly CentOS Web Panel. The sources disagree on the exact boundary:

  • NVD lists the affected range as before 1.29.
  • The VulnCheck CNA advisory lists the affected range as before 1.30.
  • VulnCheck's decoded-code analysis confirms the fix landed in 1.29 (released 31 Oct 2026), and testing of 1.30 found all ten CWP2Shell CVEs fixed with no regressions.

Builds 1.16 and 1.23 remained exploitable because backup_handler.php was unchanged and the route still reached the root shell sink without a token. To resolve the ambiguity cleanly, treat anything below 1.29 as exploitable and use CWP 1.30 or later as the remediation baseline. The current LTS release is 1.30.1.

Vendor Security History

CWP has a documented history of exploited command injection. For CVE-2022-44877, Rapid7 reports CWP issued a fix in October 2022. A researcher published a proof of concept on January 3, 2023; Shadowserver reported exploitation on January 6, and GreyNoise had observed exploiting IP addresses by January 19. That flaw allowed OS command execution through shell metacharacters in a login parameter, and CISA added it to the KEV catalog on January 17, 2023. This is documented exploitation after a correction was available, not merely an untested advisory.

For the current disclosure, VulnCheck describes a set of ten CVEs closed across a staged remediation process: the unauthenticated entry closed in 1.12, additional fixes in 1.16, API fixes in 1.29, and confirmation that all ten were fixed in 1.30. Two historical CWP entries appear in CISA KEV, CVE-2022-44877 and CVE-2025-48703, but neither is the database restore vulnerability analyzed here, and for both CISA marks known ransomware use as Unknown. There is no basis in the reviewed sources to attribute any threat actor or ransomware group to CVE-2026-77878.

References

Detect & fix
what others miss

Works with
  • GitHub
  • GitLab
  • Bitbucket
  • Azure DevOps Services
  • Gerrit
  • CVS
  • Jira
  • Linear
  • Slack
  • Security Compass
Explore integrations
Security magnifying glass visualization