Skip to content

Commit f78c915

Browse files
committed
fix(thememanager): path traversal vulnerability
1 parent cbe3e8f commit f78c915

File tree

1 file changed

+55
-12
lines changed

1 file changed

+55
-12
lines changed

includes/services/ThemeManager.php

+55-12
Original file line numberDiff line numberDiff line change
@@ -119,31 +119,74 @@ public function loadTemplates($metadata = []): ?array
119119
$this->setFavorite('preset', $this->getConfigAsStringOrDefault('favorite_preset', ''));
120120
} else {
121121
// Sinon, on récupère premièrement les valeurs passées en REQUEST, ou deuxièmement les métasdonnées présentes pour la page, ou troisièmement les valeurs du fichier de configuration
122-
if (isset($_REQUEST['theme']) && (is_dir('custom/themes/' . $_REQUEST['theme']) || is_dir('themes/' . $_REQUEST['theme']))
123-
&& isset($_REQUEST['style']) && (is_file('custom/themes/' . $_REQUEST['theme'] . '/styles/' . $_REQUEST['style']) || is_file('themes/' . $_REQUEST['theme'] . '/styles/' . $_REQUEST['style']))
124-
&& isset($_REQUEST['squelette']) && (is_file('custom/themes/' . $_REQUEST['theme'] . '/squelettes/' . $_REQUEST['squelette']) || is_file('themes/' . $_REQUEST['theme'] . '/squelettes/' . $_REQUEST['squelette']))
122+
$requested = [];
123+
$keysToVerify = ['theme', 'squelette', 'style', 'preset'];
124+
foreach ($keysToVerify as $val) {
125+
$requested[$val] = null;
126+
if (!empty($_REQUEST[$val])) {
127+
if (preg_match('/\//', $_REQUEST[$val], $matches)) {
128+
exit('ERROR: Suspicious path traversal attempt.');
129+
}
130+
switch ($val) {
131+
case 'theme':
132+
$customThemePath = basename(realpath(getcwd() . '/custom/themes/' . $_REQUEST[$val]));
133+
$classicThemePath = basename(realpath(getcwd() . '/themes/' . $_REQUEST[$val]));
134+
$requested[$val] = !empty($customThemePath) ? $customThemePath : $classicThemePath;
135+
break;
136+
137+
case 'squelette':
138+
$customPath = basename(realpath(getcwd() . '/custom/themes/' . $requested['theme'] . '/squelettes/' . $_REQUEST[$val]));
139+
$classicPath = basename(realpath(getcwd() . '/themes/' . $requested['theme'] . 'squelettes/' . $_REQUEST[$val]));
140+
$requested[$val] = null;
141+
if (!empty($customPath) && file_exists(getcwd() . '/custom/themes/' . $requested['theme'] . '/squelettes/' . $customPath)) {
142+
$requested[$val] = $customPath;
143+
} elseif (file_exists(getcwd() . '/themes/' . $requested['theme'] . '/squelettes/' . $classicPath)) {
144+
$requested[$val] = $classicPath;
145+
}
146+
if (!preg_match('/\.tpl\.html$/i', $requested[$val] ?? '', $matches)) {
147+
$requested[$val] = null;
148+
}
149+
150+
break;
151+
152+
default:
153+
// ugly append of "s" to get the path of styleS, presetS and squeletteS
154+
$customPath = basename(realpath(getcwd() . '/custom/themes/' . $requested['theme'] . '/' . $val . 's/' . $_REQUEST[$val]));
155+
$classicPath = basename(realpath(getcwd() . '/themes/' . $requested['theme'] . '/' . $val . 's/' . $_REQUEST[$val]));
156+
$requested[$val] = null;
157+
if (!empty($customPath) && file_exists(getcwd() . '/custom/themes/' . $requested['theme'] . '/' . $val . 's/' . $customPath)) {
158+
$requested[$val] = $customPath;
159+
} elseif (file_exists(getcwd() . '/themes/' . $requested['theme'] . '/' . $val . 's/' . $classicPath)) {
160+
$requested[$val] = $classicPath;
161+
}
162+
163+
break;
164+
}
165+
}
166+
}
167+
if (!empty($requested['theme']) && !empty($requested['style']) && !empty($requested['squelette']) && preg_match('/\.tpl\.html$/i', $requested['squelette'], $matches)
125168
) {
126-
$this->setFavorite('theme', $_REQUEST['theme']);
127-
$this->setFavorite('style', $_REQUEST['style']);
128-
$this->setFavorite('squelette', $_REQUEST['squelette']);
169+
$this->setFavorite('theme', $requested['theme']);
170+
$this->setFavorite('style', $requested['style']);
171+
$this->setFavorite('squelette', $requested['squelette']);
129172

130173
// presets
131-
if (isset($_REQUEST['preset'])
174+
if (!empty($requested['preset'])
132175
&& (
133176
(
134-
($isCustom = (substr($_REQUEST['preset'], 0, strlen(self::CUSTOM_CSS_PRESETS_PREFIX)) == self::CUSTOM_CSS_PRESETS_PREFIX))
135-
&& is_file(self::CUSTOM_CSS_PRESETS_PATH . '/' . substr($_REQUEST['preset'], strlen(self::CUSTOM_CSS_PRESETS_PREFIX)))
177+
($isCustom = (substr($requested['preset'], 0, strlen(self::CUSTOM_CSS_PRESETS_PREFIX)) == self::CUSTOM_CSS_PRESETS_PREFIX))
178+
&& is_file(self::CUSTOM_CSS_PRESETS_PATH . '/' . substr($requested['preset'], strlen(self::CUSTOM_CSS_PRESETS_PREFIX)))
136179
)
137180
|| (
138181
!$isCustom
139182
&& (
140-
is_file('custom/themes/' . $_REQUEST['theme'] . '/presets/' . $_REQUEST['preset'])
141-
|| is_file('themes/' . $_REQUEST['theme'] . '/presets/' . $_REQUEST['preset'])
183+
is_file('custom/themes/' . $requested['theme'] . '/presets/' . $requested['preset'])
184+
|| is_file('themes/' . $requested['theme'] . '/presets/' . $requested['preset'])
142185
)
143186
)
144187
)
145188
) {
146-
$this->setFavorite('preset', $_REQUEST['preset']);
189+
$this->setFavorite('preset', $requested['preset']);
147190
}
148191

149192
if (isset($_REQUEST['bgimg']) && is_file('files/backgrounds/' . $_REQUEST['bgimg'])) {

0 commit comments

Comments
 (0)