Skip to content

Commit 1f2d1c2

Browse files
committed
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.
1 parent 3a7f6fb commit 1f2d1c2

File tree

1 file changed

+27
-7
lines changed
  • plugin/source/dynamix.unraid.net/usr/local/emhttp/plugins/dynamix.plugin.manager/include

1 file changed

+27
-7
lines changed

plugin/source/dynamix.unraid.net/usr/local/emhttp/plugins/dynamix.plugin.manager/include/UnraidCheckExec.php

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,32 +12,52 @@
1212
class UnraidCheckExec
1313
{
1414
private const SCRIPT_PATH = '/usr/local/emhttp/plugins/dynamix.plugin.manager/scripts/unraidcheck';
15+
private const ALLOWED_DOMAIN = 'releases.unraid.net';
1516

1617
private function setupEnvironment(): void
1718
{
1819
header('Content-Type: application/json');
20+
header('X-Content-Type-Options: nosniff');
21+
header('X-Frame-Options: DENY');
22+
header('Content-Security-Policy: default-src \'none\'');
1923

2024
$params = [
2125
'json' => 'true',
2226
];
23-
// allows the web components to determine the OS_RELEASES url
24-
if (isset($_GET['altUrl']) && filter_var($_GET['altUrl'], FILTER_VALIDATE_URL)) {
25-
$params['altUrl'] = $_GET['altUrl'];
27+
28+
if (isset($_GET['altUrl'])) {
29+
$url = filter_var($_GET['altUrl'], FILTER_VALIDATE_URL);
30+
if ($url !== false) {
31+
$host = parse_url($url, PHP_URL_HOST);
32+
$scheme = parse_url($url, PHP_URL_SCHEME);
33+
34+
if ($host && $scheme === 'https' && (
35+
$host === self::ALLOWED_DOMAIN ||
36+
str_ends_with($host, '.' . self::ALLOWED_DOMAIN)
37+
)) {
38+
$params['altUrl'] = $url;
39+
}
40+
}
2641
}
27-
// pass the params to the unraidcheck script for usage in UnraidCheck.php
42+
2843
putenv('QUERY_STRING=' . http_build_query($params));
2944
}
3045

3146
public function execute(): string
3247
{
33-
if (!is_file(self::SCRIPT_PATH) || !is_readable(self::SCRIPT_PATH)) {
34-
throw new RuntimeException('Script not found or not readable');
48+
// Validate script with all necessary permissions
49+
if (!is_file(self::SCRIPT_PATH) ||
50+
!is_readable(self::SCRIPT_PATH) ||
51+
!is_executable(self::SCRIPT_PATH)) {
52+
throw new RuntimeException('Script not found or not executable');
3553
}
3654

3755
$this->setupEnvironment();
3856
$output = [];
3957
$command = escapeshellcmd(self::SCRIPT_PATH);
40-
exec($command, $output);
58+
if (exec($command, $output) === false) {
59+
throw new RuntimeException('Script execution failed');
60+
}
4161

4262
return implode("\n", $output);
4363
}

0 commit comments

Comments
 (0)