On 20 October 2026, details of a critical vulnerability in PHP became public. It is known as “PHP2Shell” (CVE-2026-109618 and CVE-2026-109619, CVSS 9.2). The flaw is in the part of PHP that parses multipart/form-data requests, the format browsers use to upload files. Attacks against it have already been seen in the wild.
This is not an ISPConfig vulnerability. It is a flaw in PHP itself, so it affects every PHP application on a server: WordPress, Joomla, Drupal, Nextcloud, online shops, forums, Roundcube, phpMyAdmin and also the ISPConfig control panel. An application does not need an upload form to be vulnerable. PHP parses a multipart request before the script itself runs, so a single request to any PHP file can be enough.
The real fix is an updated PHP version. Before it can be installed on your server, the Linux distributions and PHP repositories (Debian, Ubuntu, the sury.org PHP packages and others) have to build and publish the new packages. That can take some time, and PHP versions that are already end of life may never get a fix.
To close this gap, we publish ispconfig-multipart-guard. It stops multipart requests in the web server (nginx or Apache) before they reach PHP, for the whole server or only for selected websites, and it can be switched off again with one command. It is a temporary measure: while it is active, file uploads on the blocked websites do not work. Install the PHP updates as soon as they are available for your system, then switch the guard off.
Further reading: Sansec: PHP2Shell, Patchstack: PHP vulnerability alert.
Temporarily block multipart requests (Content-Type: multipart/..., used for file uploads) on ISPConfig web servers running nginx or Apache, for all websites or only for selected ones. Use it as an emergency mitigation while a PHP flaw in the multipart upload path, such as “PHP2Shell” (CVE-2026-109618 / CVE-2026-109619), is unpatched.
This is not a fix. It only closes the attack path until you install patched PHP packages. Update PHP as soon as your distribution or the PHP repository you use ships the fix. Then run
ispconfig-multipart-guard off.While blocking is active, nothing on the blocked vhosts can receive file uploads: CMS media uploads, contact forms with attachments, webmail attachments, phpMyAdmin imports and so on. Normal page views and normal (urlencoded) form posts keep working.
Setting session.upload_progress.enabled = Off in php.ini is sometimes suggested as an alternative. It is not sufficient on its own, because the multipart parser runs before any PHP code or session handling. Blocking the requests in the web server is the reliable option.
bash, curl (for the self-test), systemd, root accesswget -O /usr/local/sbin/ispconfig-multipart-guard https://www.ispconfig.org/downloads/ispconfig-multipart-guard.sh
chmod 755 /usr/local/sbin/ispconfig-multipart-guard
ispconfig-multipart-guard install
install does the following:
/etc/cron.d/ispconfig-multipart-guard). It runs repair every 5 minutes and re-adds hooks that an ISPConfig update or a package update removed. Use install --no-cron if you do not want it.Websites are not blocked until you ask for it:
ispconfig-multipart-guard block all # every vhost on this server
ispconfig-multipart-guard block example.com # only one website
In an ISPConfig multiserver setup, run the script on every web server. Also run it on the master if it serves the panel or apps vhost.
| Command | Description |
|---|---|
install [--no-cron] |
Install the hooks and the cron job. Blocks the panel by default. Safe to run again. |
block TARGET... |
Start blocking multipart requests for the given targets. |
unblock TARGET... |
Stop blocking for the given targets. |
off |
Stop blocking for everything. The hooks stay installed, so you can block again at any time. |
status |
Show the targets, the hook state, the allowlist and the cron job. |
sites |
List all vhosts found in the web server configuration and show whether each is blocked. |
test |
Send a multipart probe to every vhost on this server and compare the result with the targets. |
repair [--quiet] |
Re-apply the configuration, for example after a manual edit of the targets or allowlist file. Reloads the web server only when something changed. |
uninstall |
Remove all hooks, the generated configuration and the cron job. /etc/ispconfig-multipart-guard and the backups are kept. |
block and unblock run the self-test afterwards. Add --quiet to skip it.
| Target | Meaning |
|---|---|
all |
Every vhost on this server, including panel, apps and default vhost. |
panel |
The ISPConfig control panel vhost (ispconfig.vhost, port 8080 by default). |
apps |
The ISPConfig apps vhost (apps.vhost, port 8081 by default), which serves phpMyAdmin and Roundcube. |
default |
The Debian/Ubuntu default vhost (nginx: sites-enabled/default; Apache: 000-default.conf, default-ssl.conf). |
<domain> |
A website by any of its names: the domain, an alias domain or a subdomain configured as a server name or alias. www.example.com selects the same vhost as example.com. |
Targets are stored in /etc/ispconfig-multipart-guard/targets, one per line. all overrides everything else. To block everything except one website, block all other websites by name instead of using all. unblock example.com while all is set changes nothing.
A domain that matches no vhost on the server produces a warning. That is normal in a multiserver setup where the website lives on another web server.
Examples:
ispconfig-multipart-guard block all
ispconfig-multipart-guard block apps default shop.example.com example.org
ispconfig-multipart-guard unblock example.org
ispconfig-multipart-guard sites
ispconfig-multipart-guard off
/etc/ispconfig-multipart-guard/allowlist takes one IPv4/IPv6 address or CIDR range per line. Clients from these addresses may still upload while blocking is active, for example your office IP when you have to import a database dump with phpMyAdmin. Run ispconfig-multipart-guard repair after editing it.
203.0.113.10
2001:db8:1234::/48
The allowlist is checked against the client IP that the web server sees. Behind a reverse proxy or CDN (for example Cloudflare), that is the proxy’s IP unless the real client IP is restored. On nginx, use the realip module (set_real_ip_from + real_ip_header). On Apache, use mod_remoteip (RemoteIPHeader + RemoteIPTrustedProxy). Never add the CDN’s ranges to the allowlist. That would allow uploads from everyone.
Do not add 127.0.0.1 or ::1 unless you need local uploads. The self-test probes over loopback, so it can then no longer prove that blocking works (it warns about this).
nginx has no global location that runs for every vhost. The script therefore hooks into the files that every PHP location includes:
/etc/nginx/conf.d/ispconfig-multipart-guard.conf (generated) defines the variable $imgd_block at http level from
Content-Type header (multipart)geo)map "$server_name:$server_port". $server_name is the first server_name of the server block that nginx selected, not the Host header, so a client cannot pick an unblocked vhost by sending a different host name./etc/nginx/fastcgi_params and /etc/nginx/fastcgi.conf get a marked block with if ($imgd_block) { return 415 "..."; }. ISPConfig vhosts and snippets/fastcgi-php.conf include one of them in every PHP location.sites-available/apps.vhost) passes PHP requests without including fastcgi_params. It gets include /etc/nginx/ispconfig-multipart-guard-server.inc; at server level.Result: multipart requests to PHP on a blocked vhost get HTTP 415 with a short text message. Static files are not affected, because the flaw is in PHP. A vhost without PHP has nothing to protect, so the self-test reports it as OK.
/etc/apache2/conf-available/ispconfig-multipart-guard.conf (enabled with a2enconf) contains one global rule:
<If "%{HTTP:Content-Type} =~ /multipart/i && ! -R '203.0.113.10' && %{DOCUMENT_ROOT} =~ m#^(/var/www/example.com/web|...)/?$#">
Require all denied
ErrorDocument 403 "Multipart requests (file uploads) are temporarily disabled on this server (ispconfig-multipart-guard)."
</If>
DocumentRoot. The script reads the DocumentRoot of each targeted vhost from sites-enabled. With all, the DOCUMENT_ROOT condition is omitted.<If> sections after <Directory>, .htaccess and <Location>. A website’s own .htaccess or ISPConfig “Apache directives” can therefore not re-allow the request.Alias (/phpmyadmin, /webmail) on every website. On Apache, such a request is blocked if the website it arrives on is blocked. Blocking only apps does not cover https://example.com/phpmyadmin.nginx -t or apache2ctl -t. If the test fails, the previous files are restored and nothing is reloaded./var/backups/ispconfig-multipart-guard/ before each change.# BEGIN ispconfig-multipart-guard / # END ispconfig-multipart-guard. uninstall removes exactly these blocks.The script does not edit the vhost files of websites, so ISPConfig can regenerate them at any time. The exceptions:
apps.vhost (nginx) is rewritten by ISPConfig updates.fastcgi_params / fastcgi.conf can be replaced by an nginx package update if you accept the maintainer’s version.The cron job re-adds the hooks within 5 minutes. Run ispconfig-multipart-guard repair right after an ISPConfig update to close the gap at once.
New websites are picked up automatically when all is blocked (nginx and Apache) and on the next repair otherwise.
| Where | Affected while blocked |
|---|---|
panel |
DNS zone file import, logo upload in System > Interface > Main Config. Everything else in the panel works. |
apps |
Roundcube attachments, phpMyAdmin SQL import. |
| Websites | All PHP file uploads: CMS media libraries, plugin/theme uploads, forms with file fields, shop product images. |
Normal page views, logins and form posts without file fields keep working. Use the allowlist for temporary admin access.
ispconfig-multipart-guard test
The test sends a small multipart POST to /ispconfig-multipart-guard-probe.php on every vhost. It goes to 127.0.0.1, with the vhost’s real name and port via curl --resolve. It compares the answer with the targets:
RESULT EXPECT URL HTTP NOTE
[ OK ] blocked https://127.0.0.1:8080 415 panel (nginx)
[ OK ] blocked https://example.com:443 415 site (nginx)
[ OK ] open https://example.org:443 404 site (nginx)
A probe counts as “blocked” when the answer contains the guard’s message. 404 on an open vhost is expected, because the probe file does not exist.
Test from outside as well (replace the URL with a PHP page of the website):
curl -s -o /dev/null -w '%{http_code}\n' -X POST -H 'Content-Type: multipart/form-data; boundary=x' \
--data-binary $'--x--\r\n' https://example.com/index.php
# 415 (nginx) or 403 (Apache) = blocked
ispconfig-multipart-guard sites and check that the vhost is found and shows BLOCK yes. On nginx, check that its PHP location includes fastcgi_params or fastcgi.conf (nginx -T | less). Custom PHP locations in ISPConfig “nginx directives” that use fastcgi_pass without one of these includes are not covered. Add include /etc/nginx/ispconfig-multipart-guard-server.inc; there.ispconfig-multipart-guard repair.Apache 2.2 has no <If> and no way to push a rewrite rule into all vhosts. apache22-multipart-guard.sh handles these servers (install|block|unblock|status|test|uninstall, run as root):
<VirtualHost> in sites-enabled gets Include /etc/apache2/multipart-guard.inc right after its opening line. The original files are backed up to /root/multipart-guard-backup/.RewriteCond %{HTTP:Content-Type} multipart [NC] + RewriteRule .* - [F]), so blocked requests get HTTP 403. When blocking is off, the file contains only a comment.a2enmod rewrite)./etc/multipart-guard/allowlist takes exact IPs only, no CIDR ranges.install again afterwards; status shows how many vhosts still have the hook.These systems receive no PHP security updates at all. The guard only closes this one attack path; migrate the websites.
When patched PHP is installed and all PHP-FPM services are restarted:
ispconfig-multipart-guard off # keep the hooks for the next emergency
# or
ispconfig-multipart-guard uninstall # remove everything except /etc/ispconfig-multipart-guard and the backups
rm -rf /etc/ispconfig-multipart-guard /usr/local/sbin/ispconfig-multipart-guard
| Path | Purpose |
|---|---|
/etc/ispconfig-multipart-guard/targets |
Blocked targets |
/etc/ispconfig-multipart-guard/allowlist |
IPs/ranges that may still upload |
/etc/nginx/conf.d/ispconfig-multipart-guard.conf |
nginx: generated maps |
/etc/nginx/ispconfig-multipart-guard-server.inc |
nginx: server-level hook (apps vhost) |
/etc/nginx/fastcgi_params, /etc/nginx/fastcgi.conf |
nginx: marked hook block appended |
/etc/apache2/conf-available/ispconfig-multipart-guard.conf |
Apache: generated <If> rule |
/etc/cron.d/ispconfig-multipart-guard |
Repair job (every 5 minutes) |
/var/backups/ispconfig-multipart-guard/ |
Backups of edited files |