New New post: Rough Detection rule for CVE-2026-109618
Get in touch
All Blog
21 October 2026 · 4 MIN READ

Rough Detection rule for CVE-2026-109618

These rules are experimental, not necessarily novel (there are other “php spawning shell” rules out there) and come with no guarantees!

Sigma rule

title: PHP Worker Spawned Shell or Recon Utility (Possible PHP2Shell RCE)
id: 8f3b1c2a-6d4e-4a9b-bc77-2e5c9a1f0b42
status: experimental
description: |
    Detects a PHP worker (php-fpm, php-cgi, LiteSpeed lsphp, FrankenPHP, or Apache with mod_php)
    spawning a shell, interpreter, download/network tool or recon utility, or running a
    reverse-shell or temp-dir dropper one-liner. Written for CVE-2026-109618 / CVE-2026-109619
    ("php2shell"), where suspicious PHP child processes are the expected exploitation signal.
    Reported scope is PHP 5 onward with no known configuration prerequisite, so parent
    matching covers legacy (php5-*) packaging. Root cause is unconfirmed by PHP; the rule is
    deliberately behavioural. Also fires on PHP web shells and other PHP RCE.
references:
    - https://www.huntress.com/blog/php2shell-leaked-critical-php-vulnerabilities
    - https://watchtowr.com/intelligence/php-cve-2026-109618-faq/
    - https://github.com/Neo23x0/process-watcher/blob/main/emerging-threats/php-rce-october-26/README.md
    - https://github.com/Neo23x0/process-watcher/blob/main/emerging-threats/php-rce-october-26/guide.md
    - https://www.php.net/archive/2026.php#2026-10-20-1
    - https://jackalsyn.com/posts/php-up-2026.html
author: threatbear
date: 2026-10-21
modified: 2026-10-21
tags:
    - attack.initial-access
    - attack.t1190
    - attack.execution
    - attack.t1059.004
    - attack.persistence
    - attack.t1505.003
    - cve.2026-109618
    - cve.2026-109619
    - detection.emerging-threats
logsource:
    category: process_creation
    product: linux
detection:
    # Versioned binaries (php-fpm8.3, lsphp82) and vendor prefixes
    # (/opt/remi/php74/..., /opt/cpanel/ea-php81/..., /opt/plesk/php/8.1/...) need 'contains'
    parent_php:
        ParentImage|contains:
            - '/php-fpm'
            - '/php-cgi'
            - '/php5-fpm'    # PHP 5 era packaging (Debian 8 / Ubuntu 14.04): EOL, so unpatched
            - '/php5-cgi'
            - '/lsphp'
            - '/frankenphp'
    # mod_php runs PHP inside the Apache worker
    parent_modphp:
        ParentImage|endswith:
            - '/apache2'
            - '/httpd'
    # Telemetry usually reports the resolved binary (sh -> dash, nc -> nc.openbsd)
    child_tools:
        Image|endswith:
            - '/sh'
            - '/dash'
            - '/bash'
            - '/zsh'
            - '/ash'
            - '/ksh'
            - '/busybox'
            - '/curl'
            - '/wget'
            - '/nc'
            - '/nc.openbsd'
            - '/nc.traditional'
            - '/ncat'
            - '/netcat'
            - '/socat'
            - '/socat1'            # Rocky/RHEL 9
            - '/telnet'
            - '/inetutils-telnet'  # Debian 13
            - '/openssl'
            - '/whoami'
            - '/id'
            - '/uname'
            - '/hostname'
            - '/ifconfig'
            - '/base64'
            - '/mkfifo'
    # Catches versioned interpreters (python3.11, perl5.36)
    child_interpreters:
        Image|contains:
            - '/python'
            - '/perl'
            - '/ruby'
    # PHP CLI below a web worker: common for php -r reverse shells and stagers.
    # Regex keeps php-fpm/php-cgi themselves out of the match.
    child_php_cli:
        Image|re: '/php[0-9.]*$'
    # High-confidence patterns: never suppressed by the allowlist below.
    # '!notwrtbl!' is the marker from the PHP FastCGI temp-dir dropper Huntress observed.
    cmd_revshell_or_dropper:
        CommandLine|contains:
            - '/dev/tcp/'
            - '/dev/udp/'
            - 'fsockopen'
            - 'mkfifo'
            - 'bash -i'
            - 'sh -i'
            - 'nc -e'
            - 'ncat -e'
            - 'TCP-LISTEN'         # socat bind shell
            - 'EXEC:'              # socat exec to shell
            - 'pty.spawn'          # python TTY upgrade
            - 'socket.socket'      # python one-liner shells
            - '!notwrtbl!'
    # PHP exec()/system()/shell_exec()/popen()/mail() run "sh -c -- <command>" (the "--"
    # is optional for older builds). Allowlist only when the command STARTS with a known
    # tool, so "curl ...; sh #ffmpeg" can't ride along.
    filter_known_good:
        CommandLine|re: '^(/usr)?(/bin/)?(sh|dash|bash) -c (-- )?(/usr/(s)?bin/|/usr/local/bin/)?(sendmail|convert|magick|ffmpeg|ffprobe|pdftotext)( |$)'
    # ...and never when the command chains or substitutes further commands
    shell_chaining:
        CommandLine|contains:
            - ';'
            - '|'
            - '&&'
            - '`'
            - '$('
    condition: 1 of parent_* and (cmd_revshell_or_dropper or (1 of child_* and not (filter_known_good and not shell_chaining)))
falsepositives:
    - Applications that shell out to tools beyond the allowlist (git deploy hooks, backup plugins, control panels) - add anchored entries to filter_known_good
    - Non-PHP CGI scripts under Apache when mod_cgi is enabled
    - Apps that exec the PHP CLI (artisan, wp-cli, composer from admin panels) - allowlist per host
level: high

Elastic EQL - experimental

Tested in a lab with a custom php program that spawned a shell.

process where host.os.type == "linux" and event.type == "start" and
  event.action in ("exec", "exec_event", "start", "executed", "process_started") and

  /* PHP worker parents: versioned / vendor-prefixed paths, PHP 5 packaging, LiteSpeed, FrankenPHP, mod_php */
  process.parent.executable like~ (
    "*/php-fpm*", "*/php-cgi*", "*/php5-fpm*", "*/php5-cgi*", "*/lsphp*", "*/frankenphp*",
    "*/apache2", "*/httpd"
  ) and
  (
    /* High confidence: reverse/bind shells and the Huntress temp-dir dropper marker. Never allowlisted. */
    process.command_line like~ (
      "*/dev/tcp/*", "*/dev/udp/*", "*fsockopen*", "*mkfifo*", "*bash -i*", "*sh -i*",
      "*nc -e*", "*ncat -e*", "*TCP-LISTEN*", "*EXEC:*", "*pty.spawn*", "*socket.socket*",
      "*!notwrtbl!*"
    ) or
    (
      /* Shells, network/download tools, recon utilities, interpreters, PHP CLI */
      (
        process.name in (
          "sh", "dash", "bash", "zsh", "ash", "ksh", "busybox",
          "curl", "wget", "nc", "nc.openbsd", "nc.traditional", "ncat", "netcat",
          "socat", "socat1", "telnet", "inetutils-telnet", "openssl",
          "whoami", "id", "uname", "hostname", "ifconfig", "base64", "mkfifo"
        ) or
        process.name like~ ("python*", "perl*", "ruby*") or
        process.name regex~ "php[0-9.]*"
      ) and
      /* Allowlist: PHP's "sh -c -- <tool>" for mail()/ImageMagick/ffmpeg/pdftotext,
         only when the command STARTS with the tool and does not chain further commands */
      not (
        process.command_line regex~ "(/usr)?(/bin/)?(sh|dash|bash) -c (-- )?(/usr/s?bin/|/usr/local/bin/)?(sendmail|convert|magick|ffmpeg|ffprobe|pdftotext)( .*)?" and
        not process.command_line like~ ("*;*", "*|*", "*&&*", "*`*", "*$(*")
      )
    )
  )

Hopefully this will help some defenders :)