From 256f677fe092d5996baa6ed522f81b80e5d0686a Mon Sep 17 00:00:00 2001 From: HolgerHatGarKeineNode <123783602+HolgerHatGarKeineNode@users.noreply.github.com> Date: Tue, 2 Jun 2026 18:27:54 +0200 Subject: [PATCH] =?UTF-8?q?=F0=9F=90=9B=20**Handle=20Livewire=20exploit=20?= =?UTF-8?q?probes=20gracefully**=20-=20=E2=9C=85=20Added=20detection=20for?= =?UTF-8?q?=20Livewire=20exploit=20probes=20(`DirectlyCallingLifecycleHook?= =?UTF-8?q?sNotAllowedException`=20and=20magic=20method=20`MethodNotFoundE?= =?UTF-8?q?xception`)=20to=20prevent=20500=20errors.=20-=20=F0=9F=9B=A0?= =?UTF-8?q?=EF=B8=8F=20Updated=20exception=20handling=20to=20return=20a=20?= =?UTF-8?q?400=20response=20for=20probe=20requests.=20-=20=F0=9F=94=87=20S?= =?UTF-8?q?uppressed=20logging=20of=20exploit=20probe=20exceptions=20to=20?= =?UTF-8?q?reduce=20noise.=20-=20=E2=9C=85=20Added=20tests=20to=20verify?= =?UTF-8?q?=20400=20responses,=20logging=20suppression,=20and=20correct=20?= =?UTF-8?q?handling=20of=20legitimate=20exceptions.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- bootstrap/app.php | 30 ++++++++++++++- tests/Feature/LivewireExploitProbeTest.php | 44 ++++++++++++++++++++++ 2 files changed, 72 insertions(+), 2 deletions(-) create mode 100644 tests/Feature/LivewireExploitProbeTest.php diff --git a/bootstrap/app.php b/bootstrap/app.php index b78c56b..766217f 100644 --- a/bootstrap/app.php +++ b/bootstrap/app.php @@ -7,7 +7,9 @@ use Illuminate\Foundation\Application; use Illuminate\Foundation\Configuration\Exceptions; use Illuminate\Foundation\Configuration\Middleware; use Illuminate\Http\Request; +use Livewire\Exceptions\MethodNotFoundException; use Livewire\Features\SupportFileUploads\MissingFileUploadsTraitException; +use Livewire\Features\SupportLifecycleHooks\DirectlyCallingLifecycleHooksNotAllowedException; use Stefro\LaravelLangCountry\Middleware\LangCountrySession; return Application::configure(basePath: dirname(__DIR__)) @@ -66,7 +68,23 @@ return Application::configure(basePath: dirname(__DIR__)) return $e instanceof MissingFileUploadsTraitException; }; - $exceptions->report(function (Throwable $e) use ($isStaleLivewireAsset, $isStaleCompiledView, $isMissingFileUploadsTrait) { + $isLivewireExploitProbe = function (Throwable $e): bool { + // Deserialization/RCE bots probe the `livewire/update` endpoint by invoking + // protected lifecycle hooks or PHP magic methods to reach gadget chains. + // Livewire safely rejects these calls; the rejection is the bot signature, + // so we silence the resulting noise instead of reporting it as a 500. + if ($e instanceof DirectlyCallingLifecycleHooksNotAllowedException) { + return true; + } + + if ($e instanceof MethodNotFoundException) { + return (bool) preg_match('/Public method \[__/', $e->getMessage()); + } + + return false; + }; + + $exceptions->report(function (Throwable $e) use ($isStaleLivewireAsset, $isStaleCompiledView, $isMissingFileUploadsTrait, $isLivewireExploitProbe) { if ($isStaleLivewireAsset($e, request())) { return false; } @@ -78,9 +96,13 @@ return Application::configure(basePath: dirname(__DIR__)) if ($isMissingFileUploadsTrait($e)) { return false; } + + if ($isLivewireExploitProbe($e)) { + return false; + } }); - $exceptions->render(function (Throwable $e, Request $request) use ($isStaleLivewireAsset, $isStaleCompiledView, $isMissingFileUploadsTrait) { + $exceptions->render(function (Throwable $e, Request $request) use ($isStaleLivewireAsset, $isStaleCompiledView, $isMissingFileUploadsTrait, $isLivewireExploitProbe) { if ($isStaleLivewireAsset($e, $request)) { return response('', 404); } @@ -93,6 +115,10 @@ return Application::configure(basePath: dirname(__DIR__)) return response('', 400); } + if ($isLivewireExploitProbe($e)) { + return response('', 400); + } + return null; }); })->create(); diff --git a/tests/Feature/LivewireExploitProbeTest.php b/tests/Feature/LivewireExploitProbeTest.php new file mode 100644 index 0000000..d0462b1 --- /dev/null +++ b/tests/Feature/LivewireExploitProbeTest.php @@ -0,0 +1,44 @@ +get('/_test/livewire-lifecycle-probe')->status())->toBe(400); +}); + +it('returns 400 for magic-method probing instead of 500', function () { + Route::get('/_test/livewire-magic-method-probe', function () { + throw new MethodNotFoundException('__call'); + }); + + expect($this->get('/_test/livewire-magic-method-probe')->status())->toBe(400); +}); + +it('does not report Livewire exploit probes to the logs', function () { + Log::spy(); + + Route::get('/_test/livewire-probe-log', function () { + throw new DirectlyCallingLifecycleHooksNotAllowedException('dehydrate', 'auth.login'); + }); + + $this->get('/_test/livewire-probe-log')->assertStatus(400); + + Log::shouldNotHaveReceived('error'); + Log::shouldNotHaveReceived('critical'); + Log::shouldNotHaveReceived('emergency'); +}); + +it('still surfaces genuine method-not-found bugs', function () { + Route::get('/_test/livewire-real-method-not-found', function () { + throw new MethodNotFoundException('saveProfile'); + }); + + expect($this->get('/_test/livewire-real-method-not-found')->status())->not->toBe(400); +});