[TikiWiki-commits] [Git][tikiwiki/tiki][30.x] [BP][FIX] Prevent open redirect via unvalidated user-supplied URLs
"Elifeleti Mukisa Dan \(@Danelif\) via TikiWiki-cvs" <[email protected]>
| Newsgroups | gmane.comp.cms.tiki.cvs |
|---|---|
| Message-ID | <6a10ab1e39859_3819282885780@gitlab-sidekiq-low-urgency-cpu-bound-v2-5755d7f9f9-78wxs.mail> |
Elifeleti Mukisa Dan pushed to branch 30.x at Tiki Wiki CMS Groupware / Tiki Commits: 2aaddbfa by Elifeleti Mukisa Dan at 2026-05-22T19:07:28+00:00 [BP][FIX] Prevent open redirect via unvalidated user-supplied URLs --- * [FIX] Prevent open redirect via unvalidated user-supplied URLs --- * [FIX] Prevent open redirect via unvalidated user-supplied URLs (cherry picked from commit b240ff127703bcd698638f7b7cfdd48d9e9259ae) b240ff12 [FIX] Prevent open redirect via unvalidated user-supplied URLs Co-authored-by: Danelif <[email protected]> See merge request tikiwiki/tiki!10292 (cherry picked from commit 6688f26db7b2188579e63651a555b4d4c140e9a3) 899120e2 [FIX] Prevent open redirect via unvalidated user-supplied URLs Co-authored-by: Elifeleti Mukisa Dan <[email protected]> See merge request tikiwiki/tiki!10314 - - - - - 10 changed files: - installer/tiki-installer.php - lib/core/CustomRoute/CustomRoute.php - lib/core/Services/User/Controller.php - lib/tikiaccesslib.php - tiki-change_password.php - tiki-channel.php - tiki-index.php - tiki-login_scr.php - tiki-payment.php - tiki-wikiplugin_edit.php Changes: ===================================== installer/tiki-installer.php ===================================== @@ -662,7 +662,7 @@ if ($install_step == '9') { if (empty($_REQUEST['multi'])) { $userlib->user_logout($user, false, $u); // logs out then redirects to home page or $u } else { - $access->redirect('http://' . $_REQUEST['multi'] . $tikiroot . $u); // send to the selected multitiki + $access->redirect('http://' . $_REQUEST['multi'] . $tikiroot . $u, allowExternal: true); // send to the selected multitiki } exit; } ===================================== lib/core/CustomRoute/CustomRoute.php ===================================== @@ -45,7 +45,7 @@ class CustomRoute { $access = TikiLib::lib('access'); if ($redirect = $route->getRedirectPath($path)) { - $access->redirect($redirect); + $access->redirect($redirect, allowExternal: true); } else { $access->display_error($path, tra("Page cannot be found"), '404'); } ===================================== lib/core/Services/User/Controller.php ===================================== @@ -1232,7 +1232,10 @@ class Services_User_Controller private function redirectAndReturn($data = []): array { - header("Location:" . $_SERVER['HTTP_REFERER']); + $referer = $_SERVER['HTTP_REFERER'] ?? ''; + if ($referer !== '') { + TikiLib::lib('access')->redirect($referer); + } return $data; } ===================================== lib/tikiaccesslib.php ===================================== @@ -1193,7 +1193,12 @@ class TikiAccessLib extends TikiLib * @param int $code HTTP code * @param string $msgtype Type of message which determines styling (e.g., success, error, warning, etc.) */ - public function redirect($url = '', $msg = '', $code = 302, $msgtype = '') + /** + * @param bool $allowExternal Pass true only when the URL originates from a trusted source + * (admin-configured preference, inter-tiki setup, OAuth flow, etc.). + * Never pass true for URLs that are user-supplied at request time. + */ + public function redirect($url = '', $msg = '', $code = 302, $msgtype = '', bool $allowExternal = false) { global $prefs; @@ -1201,7 +1206,23 @@ class TikiAccessLib extends TikiLib return; } - // TODO: Validate URL + // Validate URL: reject absolute URLs pointing to a different host to prevent open redirects. + // $allowExternal must be explicitly set to true by callers that have already validated or + // trust the URL source (e.g. admin prefs, inter-tiki, OAuth). User-supplied URLs must + // always go through this check. + if (! $allowExternal && $url !== '') { + $parsed = parse_url($url); + if (! empty($parsed['host'])) { + $allowedHost = $_SERVER['HTTP_HOST'] ?? $_SERVER['SERVER_NAME'] ?? ''; + // Strip port from allowed host for comparison + $allowedHost = strtolower(explode(':', $allowedHost)[0]); + $targetHost = strtolower($parsed['host']); + if ($targetHost !== $allowedHost) { + // Off-site redirect — fall back to the configured home page + $url = $prefs['tikiIndex']; + } + } + } if ($url == '') { $url = $prefs['tikiIndex']; } ===================================== tiki-change_password.php ===================================== @@ -187,7 +187,7 @@ if (isset($_REQUEST["change"])) { $wizardlib->onLogin($user, $homePageUrl, $force); $accesslib = TikiLib::lib('access'); if (! empty($prefs['url_after_validation']) && ! empty($_REQUEST['new_user_validation'])) { - $access->redirect($prefs['url_after_validation']); + $access->redirect($prefs['url_after_validation'], allowExternal: true); } else { $accesslib->redirect($homePageUrl); } ===================================== tiki-channel.php ===================================== @@ -83,5 +83,5 @@ foreach ($calls as $call) { } if (isset($_REQUEST['return_uri'])) { - header("Location: {$_REQUEST['return_uri']}"); + $access->redirect($_REQUEST['return_uri']); } ===================================== tiki-index.php ===================================== @@ -332,7 +332,7 @@ if (empty($info) && ! ($user && $prefs['feature_wiki_userpage'] == 'y' && strcas } if (! $isprefixed && ! empty($prefs['url_anonymous_page_not_found']) && empty($user)) { - $access->redirect($prefs['url_anonymous_page_not_found']); + $access->redirect($prefs['url_anonymous_page_not_found'], allowExternal: true); } if ($user && $prefs['feature_wiki_userpage'] == 'y' && strcasecmp($prefs['feature_wiki_userpage_prefix'], $page) == 0) { ===================================== tiki-login_scr.php ===================================== @@ -52,7 +52,7 @@ try { $smarty->assign('create2FaCodeNormalLogin', $create2FaCodeNormalLogin); if ($prefs['login_autologin'] == 'y' && $prefs['login_autologin_redirectlogin'] == 'y' && ! empty($prefs['login_autologin_redirectlogin_url'])) { - $access->redirect($prefs['login_autologin_redirectlogin_url']); + $access->redirect($prefs['login_autologin_redirectlogin_url'], allowExternal: true); } if (isset($_REQUEST['clearmenucache'])) { ===================================== tiki-payment.php ===================================== @@ -132,7 +132,7 @@ if (isset($_GET['tx'])) { && isset($prefs['payment_paypal_pdt_redirect']) && $prefs['payment_paypal_pdt_redirect'] ) { - $access->redirect($prefs['payment_paypal_pdt_redirect'] . '?invoice=' . $invoice); + $access->redirect($prefs['payment_paypal_pdt_redirect'] . '?invoice=' . $invoice, allowExternal: true); } } } @@ -185,8 +185,7 @@ if (isset($_POST['manual_amount'], $_POST['invoice']) && preg_match('/^\d+(\.\d{ ] ); if (isset($_POST['returnurl'])) { - header('Location: ' . $_POST['returnurl']); - exit; + $access->redirect($_POST['returnurl'], allowExternal: true); } $access->redirect('tiki-payment.php?invoice=' . $_POST['invoice'], tra('Manual payment entered.')); ===================================== tiki-wikiplugin_edit.php ===================================== @@ -14,5 +14,5 @@ trigger_error(tr('Note, deprecated file tiki-wikiplugin_edit.php, code moved to TikiLib::lib('service')->render('plugin', 'replace', $jitPost); -header("Location: {$_SERVER['HTTP_REFERER']}"); -exit; +// Use the validated redirect helper to guard against open-redirect via a forged Referer header. +TikiLib::lib('access')->redirect($_SERVER['HTTP_REFERER'] ?? ''); View it on GitLab: https://gitlab.com/tikiwiki/tiki/-/commit/2aaddbfa0cd6b56d54cb2cc4273a42c8c0748a43 -- View it on GitLab: https://gitlab.com/tikiwiki/tiki/-/commit/2aaddbfa0cd6b56d54cb2cc4273a42c8c0748a43 You're receiving this email because of your account on gitlab.com. Manage all notifications: https://gitlab.com/-/profile/notifications | Help: https://gitlab.com/help _______________________________________________ TikiWiki-cvs mailing list [email protected] https://lists.sourceforge.net/lists/listinfo/tikiwiki-cvs