Author Topic: Unauthenticated root RCE on 0.9.8.1244 — a SECOND api sink survives the CVE-2025  (Read 69 times)

0 Members and 1 Guest are viewing this topic.

Offline
*
Hi CWP team,

I'm reporting an actively-exploited, unauthenticated remote command execution as root that affects a fully-updated 0.9.8.1244 panel. It looks like a variant of CVE-2025-67888 / KIS-2025-09 that the 0.9.8.1209 fix did not fully close: the patch sanitized one sink, but a second sink in the same api=1 code path is still reachable.

I'm posting here because there is no published security contact. I'm keeping the weaponized request, server identity and full logs out of this public post — I'll gladly share the complete PoC, raw logs and IOCs with a staff member privately. Please point me to a private channel (PM / email).

Summary
  • Component: CWP admin service (cwpsrv, ports 2030/2304), running as root, no ModSecurity.
  • Version affected: 0.9.8.1244 (latest at time of incident). Presumably every build >= 1209 that only patched the key sink.
  • Softaculous/SitePad NOT required (my host had neither) — unlike the original CVE-2025-67888.
  • Access: network, unauthenticated, no user interaction.
  • Impact: arbitrary command execution as root on the host (full server compromise).
  • Status: exploited in the wild in July 2026 against my server; this is not theoretical.

Technical description (sanitized)

The public CVE-2025-67888 injects through the key GET parameter of /admin/index.php?api=1&..., fixed in 0.9.8.1209.

On 0.9.8.1244 I observed a different injection point in the same handler:

Code: [Select]
GET /admin/index.php?api=1&key=<any-junk>&action=create_mysql_database&dbname=<INJECTION>
Two problems combine:

1. The API key check is broken, not just bypassed by a real key. With an empty admin_apis table (no keys configured), certain junk key values still pass the check and reach the action dispatch, while others are rejected — i.e. the validation returns success on malformed input rather than requiring a valid key.

2. The dbname value under action=create_mysql_database reaches a shell context unsanitized. A $(...) command-substitution in dbname executes as root. Other metacharacters ( ; | ) do NOT trigger it in my testing — only command substitution — consistent with the value being interpolated into a shell command string.

I reproduced it with a benign command (e.g. writing id output to a marker file); the marker was created owning uid=0(root). I am not including the live attack string here on purpose.

Important difference from CVE-2025-67888: that advisory lists Softaculous or SitePad as a prerequisite. My host had neither installed (no Softaculous/SitePad directories, no packages — verified). So this create_mysql_database sink does NOT depend on Softaculous/SitePad and is exploitable on a broader set of installs than the original CVE.

The 1209 changelog closed the key sink; this dbname/create_mysql_database path appears to have been missed. It may warrant its own CVE.

What the real attack did (for detection)

The attacker used the same request shape with a $(curl ... | bash) payload. cwpsrv (root) then fetched two remote files and installed:
  • a trojanized mod_auth_form.so Apache module doing SEO cloaking (serves casino/gambling content only to Googlebot, redirects real users via injected JS) — links libcurl, which a legitimate mod_auth_form never does;
  • a PyInstaller backdoor dropped as /usr/sbin/ntpd, persisted via a ntpd-manual.service systemd unit.
Both had faked mtimes (2024) but true ctimes matching the intrusion time. Because the cloak is intermittent (~7%, C2-controlled), file/DB/checksum hunts came back clean at first — the smoking gun was the module linking libcurl and the cwpsrv root process spawning wget in /var/log/messages.

IOCs (share widely — same campaign is hitting other CWP hosts)
  • Exploit / payload host IPs: 146.103.45.130 (payload + exploiter), 84.247.147.138, 109.123.239.143, 195.3.221.86, 165.154.203.234, 173.249.31.64, 172.235.183.127
  • Payload staged on a compromised Brazilian gov host: camaracmk.pr.gov.br / 187.77.251.90
  • Cloaking C2 / redirect domains: id8.mindunkj.com, id13.imagesstorages.com, tz.jmfwy.com/iisyn/789.js
  • Trojan module: mod_auth_form.so linking libcurl, ~70 KB, symbols is_search_engine_bot / encoded_bot_user_agents; backdoor: /usr/sbin/ntpd (Python/PyInstaller ~7 MB) + ntpd-manual.service

Mitigation that works today (for other admins reading this)

Until CWP patches, block the api parameter at the panel's front proxy. In cwpsrv/conf/cwp_panels.conf, inside the /admin and /api locations:

Code: [Select]
if ($arg_api) { return 403; }
# allow 127.0.0.1 first if you legitimately use the API (WHMCS/Blesta)

External api= -> 403; login/panel unaffected. This blocks BOTH the key and dbname sinks, not just the patched one. Also: restrict /admin by source IP, and remove Softaculous/SitePad if unused.

Ask to the CWP team
  • Please confirm and patch the create_mysql_database / dbname sink (and audit the other action= handlers reachable via api=1 for the same pattern).
  • Please fix the underlying key validation so malformed/junk keys are rejected rather than accepted.
  • Please provide a private security contact for the full PoC + logs.

Happy to help reproduce. Thanks.

Offline
***
To protect your server with an additional password for cwpsrv (nginx), please refer to this post:
https://forum.centos-webpanel.com/centos-webpanel-bugs/i-think-there-is-a-very-serious-security-vulnerability-in-cwp-right-now/msg53465/#msg53465

This is not a CWP password; it is a password for *nginx*, which cwpsrv is based on.
It restricts access to everything under your CWP server's root directory.
You are free to use IP-based access only, or password-only access if you prefer. Alternatively, you can use both IP and password authentication.
This has worked very well for me for over two years.
I haven't had any issues with intrusions since then.

Best regards,
Netino