From 1172d71d31561c4e465dabdf6b838e64de48ad16 Mon Sep 17 00:00:00 2001 From: Matthew Penner Date: Wed, 10 Apr 2024 14:41:32 -0600 Subject: [PATCH 1/3] app: improve `docker_image` validation --- app/Http/Requests/Admin/Egg/EggFormRequest.php | 2 +- .../Api/Client/Servers/Settings/SetDockerImageRequest.php | 2 +- app/Models/Egg.php | 2 +- app/Models/Server.php | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/Http/Requests/Admin/Egg/EggFormRequest.php b/app/Http/Requests/Admin/Egg/EggFormRequest.php index 0f153393f..a41ac4eba 100644 --- a/app/Http/Requests/Admin/Egg/EggFormRequest.php +++ b/app/Http/Requests/Admin/Egg/EggFormRequest.php @@ -11,7 +11,7 @@ class EggFormRequest extends AdminFormRequest $rules = [ 'name' => 'required|string|max:191', 'description' => 'nullable|string', - 'docker_images' => 'required|string', + 'docker_images' => ['required', 'string', 'max:191', 'regex:/^([a-zA-Z0-9 .#_\/\-]*)(\|*)([a-zA-Z0-9 .\/:@]*)$/'], 'force_outgoing_ip' => 'sometimes|boolean', 'file_denylist' => 'array', 'startup' => 'required|string', diff --git a/app/Http/Requests/Api/Client/Servers/Settings/SetDockerImageRequest.php b/app/Http/Requests/Api/Client/Servers/Settings/SetDockerImageRequest.php index f618de370..a104a91bc 100644 --- a/app/Http/Requests/Api/Client/Servers/Settings/SetDockerImageRequest.php +++ b/app/Http/Requests/Api/Client/Servers/Settings/SetDockerImageRequest.php @@ -24,7 +24,7 @@ class SetDockerImageRequest extends ClientApiRequest implements ClientPermission Assert::isInstanceOf($server, Server::class); return [ - 'docker_image' => ['required', 'string', Rule::in(array_values($server->egg->docker_images))], + 'docker_image' => ['required', 'string', 'max:191', 'regex:/^([a-zA-Z0-9 .#_\/\-]*)(\|*)([a-zA-Z0-9 .\/:@]*)$/', Rule::in(array_values($server->egg->docker_images))], ]; } } diff --git a/app/Models/Egg.php b/app/Models/Egg.php index 31c3e3420..40c2e3326 100644 --- a/app/Models/Egg.php +++ b/app/Models/Egg.php @@ -123,7 +123,7 @@ class Egg extends Model 'file_denylist' => 'array|nullable', 'file_denylist.*' => 'string', 'docker_images' => 'required|array|min:1', - 'docker_images.*' => 'required|string', + 'docker_images.*' => ['required', 'string', 'max:191', 'regex:/^([a-zA-Z0-9 .#_\/\-]*)(\|*)([a-zA-Z0-9 .\/:@]*)$/'], 'startup' => 'required|nullable|string', 'config_from' => 'sometimes|bail|nullable|numeric|exists:eggs,id', 'config_stop' => 'required_without:config_from|nullable|string|max:191', diff --git a/app/Models/Server.php b/app/Models/Server.php index d53af3038..ecc137f5d 100644 --- a/app/Models/Server.php +++ b/app/Models/Server.php @@ -163,7 +163,7 @@ class Server extends Model 'egg_id' => 'required|exists:eggs,id', 'startup' => 'required|string', 'skip_scripts' => 'sometimes|boolean', - 'image' => 'required|string|max:191', + 'image' => ['required', 'string', 'max:191', 'regex:/^([a-zA-Z0-9 .#_\/\-]*)(\|*)([a-zA-Z0-9 .\/:@]*)$/'], 'database_limit' => 'present|nullable|integer|min:0', 'allocation_limit' => 'sometimes|nullable|integer|min:0', 'backup_limit' => 'present|nullable|integer|min:0', From 319ca683f849e9476a55eefab49321b18ce5deea Mon Sep 17 00:00:00 2001 From: Matthew Penner Date: Wed, 10 Apr 2024 17:38:09 -0600 Subject: [PATCH 2/3] api(remote): ensure requesting node is checked --- .../Remote/Backups/BackupRemoteUploadController.php | 9 ++++++++- .../Api/Remote/Backups/BackupStatusController.php | 11 +++++++++-- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/app/Http/Controllers/Api/Remote/Backups/BackupRemoteUploadController.php b/app/Http/Controllers/Api/Remote/Backups/BackupRemoteUploadController.php index 7d92e0b1a..c3bf72662 100644 --- a/app/Http/Controllers/Api/Remote/Backups/BackupRemoteUploadController.php +++ b/app/Http/Controllers/Api/Remote/Backups/BackupRemoteUploadController.php @@ -32,6 +32,10 @@ class BackupRemoteUploadController extends Controller */ public function __invoke(Request $request, string $backup): JsonResponse { + // Get the node associated with the request. + /** @var \Pterodactyl\Models\Node $node */ + $node = $request->attributes->get('node'); + // Get the size query parameter. $size = (int) $request->query('size'); if (empty($size)) { @@ -39,7 +43,10 @@ class BackupRemoteUploadController extends Controller } /** @var \Pterodactyl\Models\Backup $backup */ - $backup = Backup::query()->where('uuid', $backup)->firstOrFail(); + $backup = Backup::query() + ->where('node_id', $node->id) + ->where('uuid', $backup) + ->firstOrFail(); // Prevent backups that have already been completed from trying to // be uploaded again. diff --git a/app/Http/Controllers/Api/Remote/Backups/BackupStatusController.php b/app/Http/Controllers/Api/Remote/Backups/BackupStatusController.php index f9c2a7932..042fbd050 100644 --- a/app/Http/Controllers/Api/Remote/Backups/BackupStatusController.php +++ b/app/Http/Controllers/Api/Remote/Backups/BackupStatusController.php @@ -30,8 +30,15 @@ class BackupStatusController extends Controller */ public function index(ReportBackupCompleteRequest $request, string $backup): JsonResponse { - /** @var \Pterodactyl\Models\Backup $model */ - $model = Backup::query()->where('uuid', $backup)->firstOrFail(); + // Get the node associated with the request. + /** @var \Pterodactyl\Models\Node $node */ + $node = $request->attributes->get('node'); + + /** @var \Pterodactyl\Models\Backup $backup */ + $backup = Backup::query() + ->where('node_id', $node->id) + ->where('uuid', $backup) + ->firstOrFail(); if ($model->is_successful) { throw new BadRequestHttpException('Cannot update the status of a backup that is already marked as completed.'); From f671046947e4695b5e1c647df79305c1cefdf817 Mon Sep 17 00:00:00 2001 From: Matthew Penner Date: Wed, 10 Apr 2024 17:39:26 -0600 Subject: [PATCH 3/3] admin: tweaks to validation and rendering --- app/Http/Controllers/Admin/Nests/EggVariableController.php | 4 ++-- app/Http/Controllers/Admin/Nests/NestController.php | 2 +- app/Http/Controllers/Admin/NodesController.php | 2 +- app/Http/Requests/Admin/Egg/EggFormRequest.php | 2 +- app/Http/Requests/Admin/Nest/StoreNestFormRequest.php | 2 +- .../Api/Client/Servers/Settings/SetDockerImageRequest.php | 2 +- app/Models/Egg.php | 2 +- app/Models/Server.php | 2 +- public/themes/pterodactyl/js/admin/new-server.js | 2 +- resources/views/admin/servers/view/startup.blade.php | 2 +- 10 files changed, 11 insertions(+), 11 deletions(-) diff --git a/app/Http/Controllers/Admin/Nests/EggVariableController.php b/app/Http/Controllers/Admin/Nests/EggVariableController.php index 40274b323..2b84e8b02 100644 --- a/app/Http/Controllers/Admin/Nests/EggVariableController.php +++ b/app/Http/Controllers/Admin/Nests/EggVariableController.php @@ -69,7 +69,7 @@ class EggVariableController extends Controller { $this->updateService->handle($variable, $request->normalize()); $this->alert->success(trans('admin/nests.variables.notices.variable_updated', [ - 'variable' => $variable->name, + 'variable' => htmlspecialchars($variable->name), ]))->flash(); return redirect()->route('admin.nests.egg.variables', $egg->id); @@ -82,7 +82,7 @@ class EggVariableController extends Controller { $this->variableRepository->delete($variable->id); $this->alert->success(trans('admin/nests.variables.notices.variable_deleted', [ - 'variable' => $variable->name, + 'variable' => htmlspecialchars($variable->name), ]))->flash(); return redirect()->route('admin.nests.egg.variables', $egg); diff --git a/app/Http/Controllers/Admin/Nests/NestController.php b/app/Http/Controllers/Admin/Nests/NestController.php index 037dd0943..311a5df28 100644 --- a/app/Http/Controllers/Admin/Nests/NestController.php +++ b/app/Http/Controllers/Admin/Nests/NestController.php @@ -56,7 +56,7 @@ class NestController extends Controller public function store(StoreNestFormRequest $request): RedirectResponse { $nest = $this->nestCreationService->handle($request->normalize()); - $this->alert->success(trans('admin/nests.notices.created', ['name' => $nest->name]))->flash(); + $this->alert->success(trans('admin/nests.notices.created', ['name' => htmlspecialchars($nest->name)]))->flash(); return redirect()->route('admin.nests.view', $nest->id); } diff --git a/app/Http/Controllers/Admin/NodesController.php b/app/Http/Controllers/Admin/NodesController.php index 573a1d9f8..71094a517 100644 --- a/app/Http/Controllers/Admin/NodesController.php +++ b/app/Http/Controllers/Admin/NodesController.php @@ -131,7 +131,7 @@ class NodesController extends Controller ['ip', '=', $request->input('ip')], ]); - $this->alert->success(trans('admin/node.notices.unallocated_deleted', ['ip' => $request->input('ip')])) + $this->alert->success(trans('admin/node.notices.unallocated_deleted', ['ip' => htmlspecialchars($request->input('ip'))])) ->flash(); return redirect()->route('admin.nodes.view.allocation', $node); diff --git a/app/Http/Requests/Admin/Egg/EggFormRequest.php b/app/Http/Requests/Admin/Egg/EggFormRequest.php index a41ac4eba..0a88f5a74 100644 --- a/app/Http/Requests/Admin/Egg/EggFormRequest.php +++ b/app/Http/Requests/Admin/Egg/EggFormRequest.php @@ -11,7 +11,7 @@ class EggFormRequest extends AdminFormRequest $rules = [ 'name' => 'required|string|max:191', 'description' => 'nullable|string', - 'docker_images' => ['required', 'string', 'max:191', 'regex:/^([a-zA-Z0-9 .#_\/\-]*)(\|*)([a-zA-Z0-9 .\/:@]*)$/'], + 'docker_images' => ['required', 'string', 'regex:/^[\w#\.\/\- ]*\|*[\w\.\/\-:@ ]*$/im'], 'force_outgoing_ip' => 'sometimes|boolean', 'file_denylist' => 'array', 'startup' => 'required|string', diff --git a/app/Http/Requests/Admin/Nest/StoreNestFormRequest.php b/app/Http/Requests/Admin/Nest/StoreNestFormRequest.php index 193f8676c..81505de15 100644 --- a/app/Http/Requests/Admin/Nest/StoreNestFormRequest.php +++ b/app/Http/Requests/Admin/Nest/StoreNestFormRequest.php @@ -9,7 +9,7 @@ class StoreNestFormRequest extends AdminFormRequest public function rules(): array { return [ - 'name' => 'required|string|min:1|max:191', + 'name' => 'required|string|min:1|max:191|regex:/^[\w\- ]+$/', 'description' => 'string|nullable', ]; } diff --git a/app/Http/Requests/Api/Client/Servers/Settings/SetDockerImageRequest.php b/app/Http/Requests/Api/Client/Servers/Settings/SetDockerImageRequest.php index a104a91bc..231fec81b 100644 --- a/app/Http/Requests/Api/Client/Servers/Settings/SetDockerImageRequest.php +++ b/app/Http/Requests/Api/Client/Servers/Settings/SetDockerImageRequest.php @@ -24,7 +24,7 @@ class SetDockerImageRequest extends ClientApiRequest implements ClientPermission Assert::isInstanceOf($server, Server::class); return [ - 'docker_image' => ['required', 'string', 'max:191', 'regex:/^([a-zA-Z0-9 .#_\/\-]*)(\|*)([a-zA-Z0-9 .\/:@]*)$/', Rule::in(array_values($server->egg->docker_images))], + 'docker_image' => ['required', 'string', 'max:191', 'regex:/^[\w#\.\/\- ]*\|*[\w\.\/\-:@ ]*$/', Rule::in(array_values($server->egg->docker_images))], ]; } } diff --git a/app/Models/Egg.php b/app/Models/Egg.php index 40c2e3326..f0e668b27 100644 --- a/app/Models/Egg.php +++ b/app/Models/Egg.php @@ -123,7 +123,7 @@ class Egg extends Model 'file_denylist' => 'array|nullable', 'file_denylist.*' => 'string', 'docker_images' => 'required|array|min:1', - 'docker_images.*' => ['required', 'string', 'max:191', 'regex:/^([a-zA-Z0-9 .#_\/\-]*)(\|*)([a-zA-Z0-9 .\/:@]*)$/'], + 'docker_images.*' => ['required', 'string', 'max:191', 'regex:/^[\w#\.\/\- ]*\|*[\w\.\/\-:@ ]*$/'], 'startup' => 'required|nullable|string', 'config_from' => 'sometimes|bail|nullable|numeric|exists:eggs,id', 'config_stop' => 'required_without:config_from|nullable|string|max:191', diff --git a/app/Models/Server.php b/app/Models/Server.php index ecc137f5d..f95e1deef 100644 --- a/app/Models/Server.php +++ b/app/Models/Server.php @@ -163,7 +163,7 @@ class Server extends Model 'egg_id' => 'required|exists:eggs,id', 'startup' => 'required|string', 'skip_scripts' => 'sometimes|boolean', - 'image' => ['required', 'string', 'max:191', 'regex:/^([a-zA-Z0-9 .#_\/\-]*)(\|*)([a-zA-Z0-9 .\/:@]*)$/'], + 'image' => ['required', 'string', 'max:191', 'regex:/^[\w\.\/\-:@ ]*$/'], 'database_limit' => 'present|nullable|integer|min:0', 'allocation_limit' => 'sometimes|nullable|integer|min:0', 'backup_limit' => 'present|nullable|integer|min:0', diff --git a/public/themes/pterodactyl/js/admin/new-server.js b/public/themes/pterodactyl/js/admin/new-server.js index db138cbde..1fd80a921 100644 --- a/public/themes/pterodactyl/js/admin/new-server.js +++ b/public/themes/pterodactyl/js/admin/new-server.js @@ -88,7 +88,7 @@ $('#pEggId').on('change', function (event) { for (let i = 0; i < keys.length; i++) { let opt = document.createElement('option'); opt.value = images[keys[i]]; - opt.innerHTML = keys[i] + " (" + images[keys[i]] + ")"; + opt.innerText = keys[i] + " (" + images[keys[i]] + ")"; $('#pDefaultContainer').append(opt); } diff --git a/resources/views/admin/servers/view/startup.blade.php b/resources/views/admin/servers/view/startup.blade.php index 05330298e..c2d6dc11e 100644 --- a/resources/views/admin/servers/view/startup.blade.php +++ b/resources/views/admin/servers/view/startup.blade.php @@ -119,7 +119,7 @@ for (let i = 0; i < keys.length; i++) { let opt = document.createElement('option'); opt.value = images[keys[i]]; - opt.innerHTML = keys[i] + " (" + images[keys[i]] + ")"; + opt.innerText = keys[i] + " (" + images[keys[i]] + ")"; if (objectChain.id === parseInt(Pterodactyl.server.egg_id) && Pterodactyl.server.image == opt.value) { opt.selected = true }