#!/bin/bash # cPanel CVE-2026-41940 IOC Detector # https://github.com/limo57640-crypto/cpanel-cve-41940-detector # https://ping7.cc/cve/cpanel-41940 # # Read-only IOC scanner for cPanel/WHM servers vulnerable to or # compromised by CVE-2026-41940 (CRLF injection authentication bypass, # CVSS 9.8) and related campaigns: # - .sorry ransomware extension files (Censys: 8,859 hosts) # - Mr_Rot13 group Filemanager backdoor (bcrypt-protected GUI) # - cPanelSniper exploitation tool artifacts # - C2 callbacks to wrned[.]com / wpsock[.]com # # Run: # curl -sSL https://raw.githubusercontent.com/limo57640-crypto/cpanel-cve-41940-detector/main/detect.sh | sudo bash # # Or download and inspect first (recommended): # wget https://raw.githubusercontent.com/limo57640-crypto/cpanel-cve-41940-detector/main/detect.sh # less detect.sh # sudo bash detect.sh # # Output: colored CLEAN / SUSPICIOUS / COMPROMISED summary # Exit codes: 0=clean, 1=suspicious, 2=compromised, 3=error set -u VERSION="1.0.0" SCRIPT_NAME="cpanel-cve-41940-detector" # --------------------------------------------------------------------------- # # Colors (skip if no TTY) # --------------------------------------------------------------------------- # if [ -t 1 ]; then RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' BOLD='\033[1m' DIM='\033[2m' NC='\033[0m' else RED='' GREEN='' YELLOW='' BLUE='' BOLD='' DIM='' NC='' fi # --------------------------------------------------------------------------- # # State # --------------------------------------------------------------------------- # SUSPICIOUS_COUNT=0 COMPROMISED_COUNT=0 CHECKS_RUN=0 FINDINGS=() # --------------------------------------------------------------------------- # # Helpers # --------------------------------------------------------------------------- # banner() { echo echo -e "${BOLD}${BLUE}╔═══════════════════════════════════════════════════════════════╗${NC}" echo -e "${BOLD}${BLUE}║ cPanel CVE-2026-41940 IOC Detector v${VERSION} ║${NC}" echo -e "${BOLD}${BLUE}║ https://ping7.cc/cve/cpanel-41940 ║${NC}" echo -e "${BOLD}${BLUE}╚═══════════════════════════════════════════════════════════════╝${NC}" echo } step() { CHECKS_RUN=$((CHECKS_RUN + 1)) printf "${BLUE}[%2d]${NC} %s ... " "$CHECKS_RUN" "$1" } ok() { echo -e "${GREEN}OK${NC}" } suspicious() { SUSPICIOUS_COUNT=$((SUSPICIOUS_COUNT + 1)) echo -e "${YELLOW}SUSPICIOUS${NC}" [ -n "${1:-}" ] && echo -e " ${YELLOW}↳ $1${NC}" [ -n "${1:-}" ] && FINDINGS+=("[SUSPICIOUS] $1") } compromised() { COMPROMISED_COUNT=$((COMPROMISED_COUNT + 1)) echo -e "${RED}${BOLD}COMPROMISED${NC}" [ -n "${1:-}" ] && echo -e " ${RED}↳ $1${NC}" [ -n "${1:-}" ] && FINDINGS+=("[COMPROMISED] $1") } skip() { echo -e "${DIM}skipped: ${1:-not applicable}${NC}" } require_root() { if [ "$(id -u)" -ne 0 ]; then echo -e "${RED}ERROR: This script must be run as root (or via sudo).${NC}" >&2 echo "Some checks read /var/log/, /etc/shadow, etc. — root required." >&2 exit 3 fi } require_cpanel() { if [ ! -x /usr/local/cpanel/cpanel ]; then echo -e "${RED}ERROR: cPanel not detected (no /usr/local/cpanel/cpanel binary).${NC}" >&2 echo "This tool is specifically for cPanel/WHM servers." >&2 exit 3 fi } # --------------------------------------------------------------------------- # # Checks # --------------------------------------------------------------------------- # check_cpanel_version() { step "cPanel/WHM build version" local version version=$(/usr/local/cpanel/cpanel -V 2>/dev/null | head -1) if [ -z "$version" ]; then suspicious "Could not read cPanel version" return fi # Patched builds (per cPanel TSR 2026-04-28): # - 11.110.0.x where x >= patched build # - 11.108.x.x and 11.106.x.x got backports # NOTE: Cross-reference https://docs.cpanel.net/changelogs/ for exact # patched build numbers. This is a heuristic check. echo echo -e " ${DIM}Detected: $version${NC}" # Check for known unpatched major versions if echo "$version" | grep -qE "11\.(102|104|106|108)\."; then # These older versions need backport — check release notes suspicious "Older major version detected — verify backport applied per cPanel TSR 2026-04-28" elif echo "$version" | grep -qE "11\.110\."; then # 11.110.x — verify build number is post-patch suspicious "11.110.x branch — verify build number is >= patched release (see cPanel TSR 2026-04-28)" else ok fi } check_sorry_files() { step ".sorry / .ENCRYPTED / ransomware extension files" local count count=$(find / -type f \( -name "*.sorry" -o -name "*.ENCRYPTED" -o -name "*.ENCRYPT" \) \ -not -path "/proc/*" -not -path "/sys/*" 2>/dev/null | wc -l) if [ "$count" -gt 0 ]; then compromised "Found $count file(s) with ransomware extensions — likely active compromise" find / -type f \( -name "*.sorry" -o -name "*.ENCRYPTED" \) \ -not -path "/proc/*" -not -path "/sys/*" 2>/dev/null | head -10 | while read -r f; do echo -e " ${RED}↳ $f${NC}" done else ok fi } check_filemanager_backdoor() { step "Mr_Rot13 Filemanager backdoor signature" local found=0 # Common backdoor paths observed in the wild (per Censys + watchTowr writeups) local paths=( "/usr/local/cpanel/3rdparty/bin/filemanager" "/usr/local/bin/filemanager" "/tmp/filemanager" "/var/tmp/filemanager" "/root/filemanager" ) for p in "${paths[@]}"; do if [ -f "$p" ]; then # Real cPanel doesn't ship a "filemanager" binary at these paths compromised "Suspicious binary at $p (not a standard cPanel file)" found=1 fi done # Look for go-built binaries with filemanager-like names if find / -type f -name "filemanager*" -executable -not -path "/proc/*" \ -not -path "/sys/*" -not -path "/usr/local/cpanel/whostmgr*" 2>/dev/null \ | head -5 | grep -q .; then found=1 fi [ "$found" -eq 0 ] && ok } check_c2_dns() { step "C2 callback domains in DNS / hosts / recent connections" local c2_domains=("wrned.com" "wpsock.com" "wrned.net") local found=0 # Check /etc/hosts for d in "${c2_domains[@]}"; do if grep -q "$d" /etc/hosts 2>/dev/null; then compromised "C2 domain $d in /etc/hosts" found=1 fi done # Check recent DNS queries (if BIND or named is running) if [ -f /var/named/data/named.run ]; then for d in "${c2_domains[@]}"; do if grep -q "$d" /var/named/data/named.run 2>/dev/null; then compromised "Recent DNS query for C2 domain $d" found=1 fi done fi # Check active connections if command -v ss >/dev/null 2>&1; then for d in "${c2_domains[@]}"; do if ss -tn 2>/dev/null | grep -q "$d"; then compromised "Active connection to C2 domain $d" found=1 fi done fi [ "$found" -eq 0 ] && ok } check_suspicious_cgi() { step "Suspicious files in cgi-bin / public_html" local found=0 local paths=("/usr/local/cpanel/cgi-sys" "/usr/local/cpanel/3rdparty/share/cgi-bin") for path in "${paths[@]}"; do [ -d "$path" ] || continue # Files newer than the cPanel install — usually indicates tampering local recent recent=$(find "$path" -type f -newer /etc/cpupdate.conf 2>/dev/null | head -10) if [ -n "$recent" ]; then suspicious "Files newer than last cPanel update in $path:" echo "$recent" | while read -r f; do echo -e " ${YELLOW}↳ $f${NC}" done found=1 fi done [ "$found" -eq 0 ] && ok } check_root_crontab() { step "Suspicious entries in root crontab / cron.d" local suspicious_patterns=( "wget.*\.sh" "curl.*\|.*bash" "/tmp/.*\.sh" "base64.*-d.*\|.*sh" "python.*-c.*exec" "perl.*-e" "nc.*-e" ) local found=0 local crontab_content="" crontab_content="$(crontab -l 2>/dev/null; cat /etc/crontab 2>/dev/null; cat /etc/cron.d/* 2>/dev/null || true)" for pat in "${suspicious_patterns[@]}"; do if echo "$crontab_content" | grep -qE "$pat"; then suspicious "Cron job matching pattern: $pat" found=1 fi done [ "$found" -eq 0 ] && ok } check_authorized_keys() { step "Unexpected SSH authorized_keys entries (root + cPanel users)" local found=0 # Root authorized_keys if [ -s /root/.ssh/authorized_keys ]; then local key_count key_count=$(grep -c "ssh-" /root/.ssh/authorized_keys 2>/dev/null || echo 0) if [ "$key_count" -gt 5 ]; then suspicious "Root has $key_count SSH keys — verify all are yours" found=1 fi fi # cPanel user authorized_keys (newly created) if [ -d /home ]; then find /home -maxdepth 3 -name "authorized_keys" -newer /etc/cpupdate.conf \ 2>/dev/null | head -5 | while read -r f; do suspicious "Recently modified authorized_keys: $f" found=1 done fi [ "$found" -eq 0 ] && ok } check_cpanel_session_anomalies() { step "cPanel session token anomalies" if [ ! -d /usr/local/cpanel/sessions ]; then skip "Session directory not found" return fi # Look for very recent root sessions from unfamiliar IPs local count count=$(find /usr/local/cpanel/sessions -type f -newer /etc/cpupdate.conf \ 2>/dev/null | wc -l) if [ "$count" -gt 100 ]; then suspicious "$count sessions newer than last cPanel update — high churn could indicate brute force or session injection" else ok fi } check_cphulkd_brute_force() { step "cphulkd brute-force log signature" local log="/var/cpanel/hulkd/db.sqlite" if [ ! -f "$log" ]; then skip "cphulkd db not found" return fi if command -v sqlite3 >/dev/null 2>&1; then local recent recent=$(sqlite3 "$log" "SELECT COUNT(*) FROM brutes WHERE LOGINTIME > strftime('%s','now','-7 days');" 2>/dev/null || echo 0) if [ "$recent" -gt 1000 ]; then suspicious "$recent brute-force attempts in last 7 days (cphulkd)" else ok fi else skip "sqlite3 not installed" fi } check_modsecurity_alerts() { step "ModSecurity recent CRLF injection alerts" local log="/usr/local/apache/logs/error_log" [ -f "$log" ] || log="/var/log/apache2/error.log" if [ ! -f "$log" ]; then skip "Apache error log not found" return fi local crlf_hits crlf_hits=$(grep -c -E "(CRLF|%0D%0A|%0d%0a)" "$log" 2>/dev/null || echo 0) if [ "$crlf_hits" -gt 50 ]; then suspicious "$crlf_hits CRLF injection attempts in apache log — possible CVE-2026-41940 probing" else ok fi } check_world_writable_root() { step "World-writable files in critical paths" local found=0 for dir in /etc /usr/local/cpanel/scripts /usr/local/cpanel/3rdparty/bin; do [ -d "$dir" ] || continue local count count=$(find "$dir" -maxdepth 2 -type f -perm -o+w 2>/dev/null | wc -l) if [ "$count" -gt 0 ]; then suspicious "$count world-writable files in $dir" found=1 fi done [ "$found" -eq 0 ] && ok } check_recent_root_logins() { step "Recent root logins from unusual sources" if ! command -v last >/dev/null 2>&1; then skip "last command not available" return fi local recent_root recent_root=$(last -a -F root 2>/dev/null | head -20 | grep -v "still logged in" | wc -l) if [ "$recent_root" -gt 15 ]; then suspicious "$recent_root recent root logins — review with: last -a -F root | head -30" else ok fi } # --------------------------------------------------------------------------- # # Summary # --------------------------------------------------------------------------- # print_summary() { echo echo -e "${BOLD}${BLUE}════════════════════════════════════════════════════════════════${NC}" echo -e "${BOLD} SUMMARY${NC}" echo -e "${BOLD}${BLUE}════════════════════════════════════════════════════════════════${NC}" echo printf " Checks run: %d\n" "$CHECKS_RUN" printf " Suspicious: %d\n" "$SUSPICIOUS_COUNT" printf " Compromised: %d\n" "$COMPROMISED_COUNT" echo if [ "$COMPROMISED_COUNT" -gt 0 ]; then echo -e "${RED}${BOLD}STATUS: COMPROMISED — IMMEDIATE ACTION REQUIRED${NC}" echo echo -e " ${RED}Your server shows strong indicators of active compromise.${NC}" echo -e " ${RED}Do NOT delete files yet — preserve IOCs for forensics.${NC}" echo echo -e " Next steps:" echo -e " 1. Take a snapshot/backup if your provider supports it" echo -e " 2. Read https://ping7.cc/cve/cpanel-41940 for incident response" echo -e " 3. Need help? lo@ping7.cc or https://ping7.cc/services" exit 2 elif [ "$SUSPICIOUS_COUNT" -gt 0 ]; then echo -e "${YELLOW}${BOLD}STATUS: SUSPICIOUS — INVESTIGATE FURTHER${NC}" echo echo -e " Some checks returned anomalies that warrant manual review." echo -e " Review findings above and the full audit guide at:" echo -e " ${BLUE}https://ping7.cc/guides/cpanel-cve-2026-41940-self-check${NC}" echo echo -e " Need a second pair of eyes? lo@ping7.cc — free 10-minute review." exit 1 else echo -e "${GREEN}${BOLD}STATUS: CLEAN${NC}" echo echo -e " All ${CHECKS_RUN} IOC checks returned clean. Your server shows no" echo -e " obvious indicators of CVE-2026-41940 compromise." echo echo -e " Recommendations:" echo -e " 1. Verify your cPanel build is the patched version (TSR 2026-04-28)" echo -e " 2. Subscribe to CVE alerts: https://ping7.cc/services" echo -e " 3. Re-run this scan weekly during heightened threat periods" exit 0 fi } # --------------------------------------------------------------------------- # # Main # --------------------------------------------------------------------------- # main() { banner require_root require_cpanel echo -e "${BOLD}Running IOC checks on $(hostname)${NC}" echo -e "${DIM}This is a READ-ONLY scan. No files will be modified.${NC}" echo check_cpanel_version check_sorry_files check_filemanager_backdoor check_c2_dns check_suspicious_cgi check_root_crontab check_authorized_keys check_cpanel_session_anomalies check_cphulkd_brute_force check_modsecurity_alerts check_world_writable_root check_recent_root_logins print_summary } main "$@"