Skip to content

Commit f51f62a

Browse files
committed
🔒 Prevent open redirects in login and autologin flows
Add erLhcoreClassUserUtils::isValidRedirectPath() to validate user-supplied redirect paths before issuing Location headers. It rejects absolute URLs, protocol-relative URLs, control characters, backslashes, and path traversal, and decodes percent-encoded input multiple times to catch encoded bypasses. Use this validation in the login and autologin modules whenever a redirect target is taken from the "r" parameter, so logged-in redirects and post-login redirects can no longer be hijacked to external sites. 🧼 Also harden form data unserialization by passing allowed_classes => false to unserialize() when decoding collected form content, preventing object injection through stored serialized data.
1 parent 7af0808 commit f51f62a

4 files changed

Lines changed: 88 additions & 9 deletions

File tree

‎lhc_web/lib/core/lhuser/lhuserutils.php‎

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,58 @@
22

33
class erLhcoreClassUserUtils
44
{
5+
/**
6+
* Validates redirect path taken from user input (e.g. login "r" parameter).
7+
* Ensures the value is a relative path inside the application and can not be used
8+
* as an open redirect (absolute URL, protocol relative URL, backslashes, path traversal).
9+
*
10+
* @param mixed $path
11+
* @return bool
12+
*/
13+
public static function isValidRedirectPath($path)
14+
{
15+
if (!is_string($path) || $path === '') {
16+
return false;
17+
}
18+
19+
// Browsers treat %2e%2e%2f as "../", decode a few times before validating
20+
for ($i = 0; $i < 3; $i++) {
21+
$decoded = rawurldecode($path);
22+
if ($decoded === $path) {
23+
break;
24+
}
25+
$path = $decoded;
26+
}
27+
28+
// Control characters, whitespace and backslashes never belong to a valid internal path
29+
if (preg_match('/[\x00-\x20\x7F\\\\]/', $path)) {
30+
return false;
31+
}
32+
33+
// Absolute URL (http://, javascript:, data:, ...) or protocol relative URL (//host)
34+
if (preg_match('#^[^/?\#]*:#', $path) || strpos($path, '//') === 0) {
35+
return false;
36+
}
37+
38+
// Path traversal above the base URL
39+
$depth = 0;
40+
foreach (explode('/', $path) as $segment) {
41+
if ($segment === '' || $segment === '.') {
42+
continue;
43+
}
44+
45+
if ($segment === '..') {
46+
if (--$depth < 0) {
47+
return false;
48+
}
49+
} else {
50+
$depth++;
51+
}
52+
}
53+
54+
return true;
55+
}
56+
557
public static function updateStats($userlist)
658
{
759
$remap = array();

‎lhc_web/lib/models/lhabstract/erlhabstractmodelformcollected.php‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ public function __get($var)
4949
case 'content_array':
5050
$content = json_decode($this->content,true);
5151
if ($content === null) {
52-
$content = unserialize($this->content);
52+
$content = unserialize($this->content, array('allowed_classes' => false));
5353
}
5454
return $this->content_array = $content;
5555

‎lhc_web/modules/lhuser/autologin.php‎

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,13 @@
3131
} else {
3232

3333
if ($currentUser->isLogged() && !empty($Params['user_parameters_unordered']['r'])) {
34-
header('Location: ' . erLhcoreClassDesign::baseurldirect('site_admin') . '/' . base64_decode(rawurldecode($Params['user_parameters_unordered']['r'])));
34+
$redirectPath = base64_decode(rawurldecode($Params['user_parameters_unordered']['r']));
35+
36+
if (!erLhcoreClassUserUtils::isValidRedirectPath($redirectPath)) {
37+
$redirectPath = '';
38+
}
39+
40+
header('Location: ' . erLhcoreClassDesign::baseurldirect('site_admin') . '/' . ltrim($redirectPath, '/'));
3541
exit;
3642
}
3743

@@ -125,7 +131,10 @@
125131
));
126132

127133
erLhcoreClassUser::instance()->setLoggedUser($userToLogin->id);
128-
header('Location: ' .erLhcoreClassDesign::baseurldirect('') . $instance->SiteAccess . '/'.ltrim($dataRequest['r'],'/'));
134+
135+
$redirectPath = isset($dataRequest['r']) && erLhcoreClassUserUtils::isValidRedirectPath($dataRequest['r']) ? $dataRequest['r'] : '';
136+
137+
header('Location: ' .erLhcoreClassDesign::baseurldirect('') . $instance->SiteAccess . '/'.ltrim($redirectPath,'/'));
129138
exit;
130139
} else {
131140
die(erTranslationClassLhTranslation::getInstance()->getTranslation('users/autologin','Could not find a provided user'));

‎lhc_web/modules/lhuser/login.php‎

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,14 @@
5050
return;
5151
} else {
5252
if ($currentUser->isLogged() && !empty($Params['user_parameters_unordered']['r'])) {
53-
header('Location: ' .erLhcoreClassDesign::baseurldirect('site_admin').'/'.base64_decode(rawurldecode($Params['user_parameters_unordered']['r'])));
53+
$redirectPath = base64_decode(rawurldecode($Params['user_parameters_unordered']['r']));
54+
55+
if (erLhcoreClassUserUtils::isValidRedirectPath($redirectPath)) {
56+
header('Location: ' .erLhcoreClassDesign::baseurldirect('site_admin').'/'.$redirectPath);
57+
exit;
58+
}
59+
60+
header('Location: ' .erLhcoreClassDesign::baseurldirect('site_admin'));
5461
exit;
5562
}
5663

@@ -62,7 +69,13 @@
6269
}
6370

6471
} elseif ($currentUser->isLogged() && !empty($Params['user_parameters_unordered']['r'])) {
65-
header('Location: ' .erLhcoreClassDesign::baseurldirect('') . $possibleLoginSiteAccess[0] . '/'.base64_decode(rawurldecode($Params['user_parameters_unordered']['r'])));
72+
$redirectPath = base64_decode(rawurldecode($Params['user_parameters_unordered']['r']));
73+
74+
if (!erLhcoreClassUserUtils::isValidRedirectPath($redirectPath)) {
75+
$redirectPath = '';
76+
}
77+
78+
header('Location: ' .erLhcoreClassDesign::baseurldirect('') . $possibleLoginSiteAccess[0] . '/'.ltrim($redirectPath,'/'));
6679
exit;
6780
}
6881

@@ -227,11 +240,16 @@
227240
}
228241

229242
if ($redirect != '') {
230-
erLhcoreClassModule::redirect(base64_decode($redirect));
231-
} else {
232-
erLhcoreClassModule::redirect();
233-
exit;
243+
$redirectPath = base64_decode($redirect);
244+
245+
if (erLhcoreClassUserUtils::isValidRedirectPath($redirectPath)) {
246+
erLhcoreClassModule::redirect($redirectPath);
247+
exit;
248+
}
234249
}
250+
251+
erLhcoreClassModule::redirect();
252+
exit;
235253
}
236254
}
237255

0 commit comments

Comments
 (0)