diff --git a/app/Http/Requests/PDFConfigurationRequest.php b/app/Http/Requests/PDFConfigurationRequest.php index 3e6ef3ec..af01ffb1 100644 --- a/app/Http/Requests/PDFConfigurationRequest.php +++ b/app/Http/Requests/PDFConfigurationRequest.php @@ -2,6 +2,7 @@ namespace App\Http\Requests; +use App\Rules\PublicHttpUrl; use Illuminate\Foundation\Http\FormRequest; class PDFConfigurationRequest extends FormRequest @@ -37,6 +38,7 @@ class PDFConfigurationRequest extends FormRequest 'gotenberg_host' => [ 'required', 'url', + new PublicHttpUrl, ], 'gotenberg_papersize' => [ 'required', diff --git a/app/Support/Pdf/GotenbergPdfDriver.php b/app/Support/Pdf/GotenbergPdfDriver.php index 0099d69a..be85a77c 100644 --- a/app/Support/Pdf/GotenbergPdfDriver.php +++ b/app/Support/Pdf/GotenbergPdfDriver.php @@ -2,6 +2,8 @@ namespace App\Support\Pdf; +use App\Support\Net\BlockedUrlException; +use App\Support\Net\PrivateNetworkGuard; use Gotenberg\Gotenberg; use Gotenberg\Stream; @@ -15,6 +17,16 @@ class GotenbergPdfDriver } $host = config('pdf.connections.gotenberg.host'); + + // SSRF guard: gotenberg_host is an admin-supplied URL the server POSTs + // the rendered HTML to. Block private/reserved/link-local targets even + // if set via env/seed/stale config or reachable through DNS rebinding. + try { + PrivateNetworkGuard::assertAllowed((string) $host); + } catch (BlockedUrlException $e) { + throw new \InvalidArgumentException('Invalid Gotenberg host: '.$e->getMessage()); + } + $request = Gotenberg::chromium($host) ->pdf() ->margins(0, 0, 0, 0) diff --git a/tests/Unit/GotenbergHostValidationTest.php b/tests/Unit/GotenbergHostValidationTest.php new file mode 100644 index 00000000..d517106e --- /dev/null +++ b/tests/Unit/GotenbergHostValidationTest.php @@ -0,0 +1,25 @@ + 'gotenberg'])->rules(); + + $validator = Validator::make(['gotenberg_host' => $url], ['gotenberg_host' => $rules['gotenberg_host']]); + + expect($validator->fails())->toBeTrue(); +})->with([ + 'http://127.0.0.1', + 'http://169.254.169.254', + 'http://10.0.0.5', + 'http://192.168.1.1', +]); + +test('gotenberg host allows a public address', function () { + $rules = PDFConfigurationRequest::create('/', 'POST', ['pdf_driver' => 'gotenberg'])->rules(); + + $validator = Validator::make(['gotenberg_host' => 'http://8.8.8.8'], ['gotenberg_host' => $rules['gotenberg_host']]); + + expect($validator->errors()->has('gotenberg_host'))->toBeFalse(); +});