From 83149812458f611886530de510e3eee7984c35ab Mon Sep 17 00:00:00 2001 From: Zack Spear Date: Thu, 20 Mar 2025 17:02:14 -0700 Subject: [PATCH 1/6] feat: UnraidCheckExec for Check OS Updates functionality - Added `UnraidCheckExec.php` to separate concerns between UnraidCheck and ReplaceKey, allowing for JSON responses. - Updated `unraidcheck` script to parse query strings for compatibility with the new class. - Modified `webgui.ts` to call `UnraidCheckExec.php` instead of `UnraidCheck.php` for update checks. --- .../include/UnraidCheckExec.php | 30 +++++++++++++++++++ .../scripts/unraidcheck | 3 ++ web/composables/services/webgui.ts | 2 +- 3 files changed, 34 insertions(+), 1 deletion(-) create mode 100644 plugin/source/dynamix.unraid.net/usr/local/emhttp/plugins/dynamix.plugin.manager/include/UnraidCheckExec.php diff --git a/plugin/source/dynamix.unraid.net/usr/local/emhttp/plugins/dynamix.plugin.manager/include/UnraidCheckExec.php b/plugin/source/dynamix.unraid.net/usr/local/emhttp/plugins/dynamix.plugin.manager/include/UnraidCheckExec.php new file mode 100644 index 0000000000..828003b44e --- /dev/null +++ b/plugin/source/dynamix.unraid.net/usr/local/emhttp/plugins/dynamix.plugin.manager/include/UnraidCheckExec.php @@ -0,0 +1,30 @@ +setupEnvironment(); + $output = []; + exec(self::SCRIPT_PATH, $output); + return implode("\n", $output); + } +} + +// Usage +$checker = new UnraidCheckExec(); +echo $checker->execute(); diff --git a/plugin/source/dynamix.unraid.net/usr/local/emhttp/plugins/dynamix.plugin.manager/scripts/unraidcheck b/plugin/source/dynamix.unraid.net/usr/local/emhttp/plugins/dynamix.plugin.manager/scripts/unraidcheck index b1006b9589..81cb3ea588 100755 --- a/plugin/source/dynamix.unraid.net/usr/local/emhttp/plugins/dynamix.plugin.manager/scripts/unraidcheck +++ b/plugin/source/dynamix.unraid.net/usr/local/emhttp/plugins/dynamix.plugin.manager/scripts/unraidcheck @@ -18,6 +18,9 @@ require_once "$docroot/plugins/dynamix/include/ReplaceKey.php"; $replaceKey = new ReplaceKey(); $replaceKey->check(); +// utilized by UnraidCheckExec.php to have UnraidCheck.php return a json response when this script is called directly +parse_str(getenv('QUERY_STRING') ?? '', $_GET); + require_once "$docroot/plugins/dynamix.plugin.manager/include/UnraidCheck.php"; $unraidOsCheck = new UnraidOsCheck(); $unraidOsCheck->checkForUpdate(); diff --git a/web/composables/services/webgui.ts b/web/composables/services/webgui.ts index 3fee755178..01d74ee1f6 100644 --- a/web/composables/services/webgui.ts +++ b/web/composables/services/webgui.ts @@ -108,7 +108,7 @@ export const WebguiCheckForUpdate = async (): Promise { From 3e196a3ac7dd7265001c46ee6934cece719d5c42 Mon Sep 17 00:00:00 2001 From: Zack Spear Date: Thu, 20 Mar 2025 17:22:33 -0700 Subject: [PATCH 2/6] feat: Enhance UnraidCheckExec with altUrl param with validation - Updated `UnraidCheckExec.php` to handle `altUrl` and `json` parameters for improved flexibility in OS update checks. - Modified `webgui.ts` to utilize the new `WebguiUnraidCheckExecPayload` interface, ensuring compatibility with the updated parameters. --- .../include/UnraidCheckExec.php | 14 +++++++++++++- web/composables/services/webgui.ts | 8 ++++++-- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/plugin/source/dynamix.unraid.net/usr/local/emhttp/plugins/dynamix.plugin.manager/include/UnraidCheckExec.php b/plugin/source/dynamix.unraid.net/usr/local/emhttp/plugins/dynamix.plugin.manager/include/UnraidCheckExec.php index 828003b44e..713148ac46 100644 --- a/plugin/source/dynamix.unraid.net/usr/local/emhttp/plugins/dynamix.plugin.manager/include/UnraidCheckExec.php +++ b/plugin/source/dynamix.unraid.net/usr/local/emhttp/plugins/dynamix.plugin.manager/include/UnraidCheckExec.php @@ -5,6 +5,9 @@ * handles both operations in a simplified manner. * * It's called via the WebguiCheckForUpdate function in composables/services/webgui.ts of the web components. + * Handles WebguiUnraidCheckExecPayload interface parameters: + * - altUrl?: string + * - json?: boolean */ class UnraidCheckExec { @@ -13,7 +16,16 @@ class UnraidCheckExec private function setupEnvironment(): void { header('Content-Type: application/json'); - putenv('QUERY_STRING=json=true'); + + $params = [ + 'json' => 'true', + ]; + // allows the web components to determine the OS_RELEASES url + if (isset($_GET['altUrl']) && filter_var($_GET['altUrl'], FILTER_VALIDATE_URL)) { + $params['altUrl'] = $_GET['altUrl']; + } + // pass the params to the unraidcheck script for usage in UnraidCheck.php + putenv('QUERY_STRING=' . http_build_query($params)); } public function execute(): string diff --git a/web/composables/services/webgui.ts b/web/composables/services/webgui.ts index 01d74ee1f6..a84518a6ae 100644 --- a/web/composables/services/webgui.ts +++ b/web/composables/services/webgui.ts @@ -92,6 +92,11 @@ interface WebguiUnraidCheckPayload { version?: string; } +interface WebguiUnraidCheckExecPayload { + altUrl?: string; + json?: boolean; +} + interface WebguiUnraidCheckIgnoreResponse { updateOsIgnoredReleases: string[]; } @@ -99,8 +104,7 @@ interface WebguiUnraidCheckIgnoreResponse { export const WebguiCheckForUpdate = async (): Promise => { console.debug('[WebguiCheckForUpdate]'); try { - const params: WebguiUnraidCheckPayload = { - action: 'check', + const params: WebguiUnraidCheckExecPayload = { json: true, }; // conditionally add altUrl if OS_RELEASES.toString() is not 'https://releases.unraid.net/os' From 73870226254ee17d8094f7bb4370ef1a837cf9c4 Mon Sep 17 00:00:00 2001 From: Zack Spear Date: Thu, 20 Mar 2025 17:26:00 -0700 Subject: [PATCH 3/6] feat: Update dynamix.unraid.net.plg to include UnraidCheckExec.php in restore and preserve files - Added `UnraidCheckExec.php` to the list of files restored during plugin updates. - Updated preservation rules to prevent downgrade for `UnraidCheckExec.php`. --- plugin/plugins/dynamix.unraid.net.plg | 2 ++ 1 file changed, 2 insertions(+) diff --git a/plugin/plugins/dynamix.unraid.net.plg b/plugin/plugins/dynamix.unraid.net.plg index 72a9c4d69d..3e2f5d349e 100755 --- a/plugin/plugins/dynamix.unraid.net.plg +++ b/plugin/plugins/dynamix.unraid.net.plg @@ -392,6 +392,7 @@ if [ -f /tmp/restore-files-dynamix-unraid-net ]; then "/usr/local/emhttp/plugins/dynamix.plugin.manager/scripts/showchanges" "/usr/local/emhttp/plugins/dynamix.plugin.manager/scripts/unraidcheck" "/usr/local/emhttp/plugins/dynamix.plugin.manager/include/UnraidCheck.php" + "/usr/local/emhttp/plugins/dynamix.plugin.manager/include/UnraidCheckExec.php" "/usr/local/emhttp/plugins/dynamix.my.servers/Connect.page" "/usr/local/emhttp/plugins/dynamix.my.servers/MyServers.page" "/usr/local/emhttp/plugins/dynamix.my.servers/Registration.page" @@ -511,6 +512,7 @@ preserveFilesDirs=( "move:/usr/local/emhttp/plugins/dynamix.plugin.manager/Update.page:preventDowngrade" "move:/usr/local/emhttp/plugins/dynamix.plugin.manager/scripts/unraidcheck:preventDowngrade" "move:/usr/local/emhttp/plugins/dynamix.plugin.manager/include/UnraidCheck.php:preventDowngrade" + "move:/usr/local/emhttp/plugins/dynamix.plugin.manager/include/UnraidCheckExec.php:preventDowngrade" "move:/usr/local/emhttp/plugins/dynamix.my.servers/MyServers.page:skip" "move:/usr/local/emhttp/plugins/dynamix.my.servers/Connect.page:skip" "move:/usr/local/emhttp/plugins/dynamix.my.servers/Registration.page:preventDowngrade" From 1481150ab4961e623cfbe8a7dc06d7a00af0cdea Mon Sep 17 00:00:00 2001 From: Zack Spear Date: Thu, 20 Mar 2025 17:31:24 -0700 Subject: [PATCH 4/6] fix: Remove unnecessary todo comment in webgui.ts --- web/composables/services/webgui.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/composables/services/webgui.ts b/web/composables/services/webgui.ts index a84518a6ae..04bd482694 100644 --- a/web/composables/services/webgui.ts +++ b/web/composables/services/webgui.ts @@ -112,7 +112,7 @@ export const WebguiCheckForUpdate = async (): Promise { From 3a7f6fbf663457c269496cbda3225146a6c02c66 Mon Sep 17 00:00:00 2001 From: Zack Spear Date: Fri, 21 Mar 2025 10:26:12 -0700 Subject: [PATCH 5/6] feat: Enhance UnraidCheckExec error handling and command execution --- .../dynamix.plugin.manager/include/UnraidCheckExec.php | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/plugin/source/dynamix.unraid.net/usr/local/emhttp/plugins/dynamix.plugin.manager/include/UnraidCheckExec.php b/plugin/source/dynamix.unraid.net/usr/local/emhttp/plugins/dynamix.plugin.manager/include/UnraidCheckExec.php index 713148ac46..5a3e04bd23 100644 --- a/plugin/source/dynamix.unraid.net/usr/local/emhttp/plugins/dynamix.plugin.manager/include/UnraidCheckExec.php +++ b/plugin/source/dynamix.unraid.net/usr/local/emhttp/plugins/dynamix.plugin.manager/include/UnraidCheckExec.php @@ -30,9 +30,15 @@ private function setupEnvironment(): void public function execute(): string { + if (!is_file(self::SCRIPT_PATH) || !is_readable(self::SCRIPT_PATH)) { + throw new RuntimeException('Script not found or not readable'); + } + $this->setupEnvironment(); $output = []; - exec(self::SCRIPT_PATH, $output); + $command = escapeshellcmd(self::SCRIPT_PATH); + exec($command, $output); + return implode("\n", $output); } } From 1f2d1c291a374021f4cb75f033d9642d7878c2a9 Mon Sep 17 00:00:00 2001 From: Zack Spear Date: Fri, 21 Mar 2025 10:50:06 -0700 Subject: [PATCH 6/6] feat: Enhance UnraidCheckExec with security headers and altUrl validation - Added security headers to the setupEnvironment method for improved security. - Enhanced altUrl validation to ensure only allowed domains are accepted. - Updated script execution checks to include executable permission validation. --- .../include/UnraidCheckExec.php | 34 +++++++++++++++---- 1 file changed, 27 insertions(+), 7 deletions(-) diff --git a/plugin/source/dynamix.unraid.net/usr/local/emhttp/plugins/dynamix.plugin.manager/include/UnraidCheckExec.php b/plugin/source/dynamix.unraid.net/usr/local/emhttp/plugins/dynamix.plugin.manager/include/UnraidCheckExec.php index 5a3e04bd23..7b2881cd23 100644 --- a/plugin/source/dynamix.unraid.net/usr/local/emhttp/plugins/dynamix.plugin.manager/include/UnraidCheckExec.php +++ b/plugin/source/dynamix.unraid.net/usr/local/emhttp/plugins/dynamix.plugin.manager/include/UnraidCheckExec.php @@ -12,32 +12,52 @@ class UnraidCheckExec { private const SCRIPT_PATH = '/usr/local/emhttp/plugins/dynamix.plugin.manager/scripts/unraidcheck'; + private const ALLOWED_DOMAIN = 'releases.unraid.net'; private function setupEnvironment(): void { header('Content-Type: application/json'); + header('X-Content-Type-Options: nosniff'); + header('X-Frame-Options: DENY'); + header('Content-Security-Policy: default-src \'none\''); $params = [ 'json' => 'true', ]; - // allows the web components to determine the OS_RELEASES url - if (isset($_GET['altUrl']) && filter_var($_GET['altUrl'], FILTER_VALIDATE_URL)) { - $params['altUrl'] = $_GET['altUrl']; + + if (isset($_GET['altUrl'])) { + $url = filter_var($_GET['altUrl'], FILTER_VALIDATE_URL); + if ($url !== false) { + $host = parse_url($url, PHP_URL_HOST); + $scheme = parse_url($url, PHP_URL_SCHEME); + + if ($host && $scheme === 'https' && ( + $host === self::ALLOWED_DOMAIN || + str_ends_with($host, '.' . self::ALLOWED_DOMAIN) + )) { + $params['altUrl'] = $url; + } + } } - // pass the params to the unraidcheck script for usage in UnraidCheck.php + putenv('QUERY_STRING=' . http_build_query($params)); } public function execute(): string { - if (!is_file(self::SCRIPT_PATH) || !is_readable(self::SCRIPT_PATH)) { - throw new RuntimeException('Script not found or not readable'); + // Validate script with all necessary permissions + if (!is_file(self::SCRIPT_PATH) || + !is_readable(self::SCRIPT_PATH) || + !is_executable(self::SCRIPT_PATH)) { + throw new RuntimeException('Script not found or not executable'); } $this->setupEnvironment(); $output = []; $command = escapeshellcmd(self::SCRIPT_PATH); - exec($command, $output); + if (exec($command, $output) === false) { + throw new RuntimeException('Script execution failed'); + } return implode("\n", $output); }