CVE-2026-41940 – WHM & cPanel Authentication Bypass, Checking for Indicators of Compromise

  • Checking using the official cPanel script
  • Checking the access logs
  • How to check the last time WHM was upgraded

Checking using the official cPanel script

The instructions for using the official script are on the cPanel article.

https://support.cpanel.net/hc/en-us/articles/40073787579671-Security-CVE-2026-41940-cPanel-WHM-WP2-Security-Update-04-28-2026

Copy and paste the script into a shell file. Run the script.

Here is a copy of the script for convenience.

#!/bin/bash
# Scan for compromised session files

SESSIONS_DIR="/var/cpanel/sessions"
COMPROMISED=0

echo "[*] Scanning session files for injection indicators..."

for session_file in "$SESSIONS_DIR"/raw/*; do
    [ -f "$session_file" ] || continue
    session_name=$(basename "$session_file")

    # Check if this session is/was pre-auth
    preauth_file="$SESSIONS_DIR/preauth/$session_name"

    # IOC 0: Session has both token_denied AND cp_security_token and method=badpass origin (strong indicator of exploitation)
    #
    # token_denied is set by do_token_denied() in cpsrvd when a request
    # supplies an incorrect security token. cp_security_token is the
    # attacker-injected token value. This combination indicates:
    #
    #   1. Attacker injected a cp_security_token via newline payload
    #   2. Attacker attempted to use the injected token
    #   3. cpsrvd recorded the token mismatch (token_denied counter)
    #      during the exploitation window before the session was
    #      fully promoted
    #
    # In a legitimate session:
    #   - token_denied is only present after a user-initiated
    #     security token failure (rare, typically from expired bookmarks)
    #   - It would never co-exist with a badpass origin AND an
    #     attacker-controlled cp_security_token
    #
    # This IOC catches BOTH successful and failed exploitation attempts.
    if grep -q '^token_denied=' "$session_file" && \
       grep -q '^cp_security_token=' "$session_file"; then

        # Extract values for triage context
        token_val=$(grep '^cp_security_token=' "$session_file" | head -1 | cut -d= -f2)
        denied_val=$(grep '^token_denied=' "$session_file" | head -1 | cut -d= -f2)
        origin=$(grep '^origin_as_string=' "$session_file" | head -1 | cut -d= -f2-)
        used=$(grep -a "$token_val" /usr/local/cpanel/logs/access_log | grep -m1 " 200 ")
        external_auth=$(grep '^successful_external_auth_with_timestamp=' "$session_file")

        # High confidence if origin is badpass (session was pre-auth)
        if grep -q '^origin_as_string=.*method=badpass' "$session_file"; then
                if [ -z "$external_auth" ] && [ -z "$used" ]; then
                        echo "Found possible injected session file: $session_file"
                        echo "  - No sign of usage"
                else
                    echo "[!] CRITICAL: Exploitation artifact - token_denied with injected cp_security_token: $session_file"
                    echo "    - cp_security_token=$token_val"
                    echo "    - token_denied=$denied_val"
                    echo "    - origin=$origin"
                    echo "    - Verdict: Session was pre-auth (badpass origin) with attacker-injected token"
                    echo "    - USED:  $used"
                    COMPROMISED=1
                fi
        # Medium confidence but still suspicious for any session
        else
            echo "[!] WARNING: Suspicious session with token_denied + cp_security_token: $session_file"
            echo "    - cp_security_token=$token_val"
            echo "    - token_denied=$denied_val"
            echo "    - origin=$origin"
            echo "    - Review manually: may be legitimate token expiration or exploitation attempt"
        fi
    fi

    # IOC 1: Pre-auth session with authenticated attributes
    if [ -f "$preauth_file" ]; then
        if grep -qE '^successful_external_auth_with_timestamp=' "$session_file"; then
            echo "[!] CRITICAL: Injected session detected: $session_file"
            echo "    - Contains 'successful_external_auth_with_timestamp' in pre-auth session"
            COMPROMISED=1
        fi
    fi

    # IOC 2: Any session with tfa_verified but no valid origin
    if grep -q '^tfa_verified=1' "$session_file" && \
       ! grep -q '^origin_as_string=.*method=handle_form_login' "$session_file" && \
       ! grep -q '^origin_as_string=.*method=create_user_session' "$session_file" && \
       ! grep -q '^origin_as_string=.*method=handle_auth_transfer' "$session_file"; then
        echo "[!] WARNING: Session with tfa_verified but suspicious origin: $session_file"
        COMPROMISED=1
    fi

    # IOC 3: Password field containing newlines (corrupted session file)
    if grep -qzP '(?m)^pass=.*\n.' "$session_file" 2>/dev/null; then
        echo "[!] CRITICAL: Multi-line pass value detected: $session_file"
        COMPROMISED=1
    fi
done

if [ "$COMPROMISED" -eq 0 ]; then
    echo ""
    echo "[+] No indicators of compromise found."
else
    echo ""
    echo "[!] INDICATORS OF COMPROMISE DETECTED - IMMEDIATE ACTION REQUIRED"
    echo "    1. Purge all affected sessions"
    echo "    2. Force password reset for root and all WHM users"
    echo "    3. Audit /var/log/wtmp and WHM access logs for unauthorized access"
    echo "    4. Check for persistence mechanisms (cron, SSH keys, backdoors)"
fi

Example output:

[!] WARNING: Suspicious session with token_denied + cp_security_token: /var/cpanel/sessions/raw/bob@incredigeek.com:v_u2HprLu6MtCvPP
    - cp_security_token=/cpsess1634897720
    - token_denied=1
    - origin=address=192.168.1.12,app=webmaild,creator=bob@incredigeek.com,method=handle_form_login,path=form,possessed=0
    - Review manually: may be legitimate token expiration or exploitation attempt
[!] CRITICAL: Multi-line pass value detected: /var/cpanel/sessions/raw/bob@incredigeek.com:v_u2HprLu6MtCvPP
Found possible injected session file: /var/cpanel/sessions/raw/:QM4Mx7bOR3BF7hRv
   - No sign of usage
[!] CRITICAL: Multi-line pass value detected: /var/cpanel/sessions/raw/:QM4Mx7bOR3BF7hRv
[!] CRITICAL: Exploitation artifact - token_denied with injected cp_security_token: /var/cpanel/sessions/raw/:Q3f8Ag2epeBuTaIZ 
   - cp_security_token=/cpsess04396539398 
   - token_denied=1 
   - origin=address=100.96.3.23,app=whostmgrd,method=badpass 
   - Verdict: Session was pre-auth (badpass origin) with attacker-injected token 
[!] WARNING: Session with tfa_verified but suspicious origin: /var/cpanel/sessions/raw/:Q3f8Ag2epeBuTaIZ 

[!] INDICATORS OF COMPROMISE DETECTED - IMMEDIATE ACTION REQUIRED 
   1. Purge all affected sessions 
   2. Force password reset for root and all WHM users 
   3. Audit /var/log/wtmp and WHM access logs for unauthorized access 
   4. Check for persistence mechanisms (cron, SSH keys, backdoors)

Some of the results may be false positives, which means you may have to manually check and see if results are valid or not.

ℹ️ It appears that there can be a limit to the total number of sessions available on a system. You may want to double check how old the session files are (ls -lt /var/cpanel/sessions/raw) to ensure you catch all login attempts. It is theoretically possible that the system was compromised, but the session that did it is no longer available, meaning it would not show up in the script provided by cPanel. You should still be able to check the access_logs.

Checking the access logs

This may not be the most fool proof way to check for a compromise, but it may help identify if an IP address has been authenticated and accessed restricted endpoints.

Replace valid-ip-address1 and valid-ip-address2 with your IP addresses.

grep "listacct\|terminal\|json-api" /usr/local/cpanel/logs/access_log |  grep -v "403\|401" | grep -v "valid-ip-address1\|valid-ip-address2"

Basically, we are searching for any valid requests that contain listacct, terminal, or json-api.

How to check the last time WHM was upgraded

If your server is set to automatically update WHM, it may be beneficial to know when it last updated. You can check the update logs in /var/cpanel/updatelogs to check the time and version.

As a shortcut, you can use the following command to group all of the version changes from all of the update log files.

head /var/cpanel/updatelogs/update.*.log -n 20 | grep version | sort

Example output:

[2026-04-29 23:12:04 -0000]   Running version '11.134.0.19' of updatenow.
[2026-04-29 23:12:04 -0000]   Become an updatenow.static for version: 11.134.0.20
[2026-04-29 23:12:04 -0000]   Detected version '11.134.0.19' from version file.
[2026-04-29 23:12:04 -0000]   Switching to version 11.134.0.20 of updatenow to determine if we can reach that version without failure.
[2026-04-29 23:12:04 -0000]   Target version set to '11.134.0.20'
[2026-04-30 23:12:04 -0000]   Detected version '11.134.0.20' from version file.
[2026-04-30 23:12:04 -0000]   Running version '11.134.0.20' of updatenow.
[2026-04-30 23:12:04 -0000]   Target version set to '11.134.0.20'

Links:

https://labs.watchtowr.com/the-internet-is-falling-down-falling-down-falling-down-cpanel-whm-authentication-bypass-cve-2026-41940

https://github.com/watchtowrlabs/watchTowr-vs-cPanel-WHM-AuthBypass-to-RCE.py

https://hadrian.io/blog/cve-2026-41940-a-critical-authentication-bypass-in-cpanel