From 57acd8378e263f6abd41a427f01fdcada0391a20 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jaime=20P=C3=A9rez=20Crespo?= <jaime.perez@uninett.no>
Date: Fri, 24 Jan 2014 16:36:54 +0000
Subject: [PATCH] Followup on previous commits. Use redirectUntrustedURL() as a
 shortcut, and let everything else make use of redirectTrustedURL(). Move the
 responsibility to check the input out of the library, to the places where
 URLs are grabbed from input parameters.

git-svn-id: https://simplesamlphp.googlecode.com/svn/trunk@3332 44740490-163a-0410-bde0-09ae8108e29a
---
 config-templates/config.php                   |  21 +-
 docs/simplesamlphp-sp-migration.txt           |   3 +
 lib/SimpleSAML/Auth/BWC.php                   |   6 +-
 lib/SimpleSAML/Auth/Default.php               |  64 +++--
 lib/SimpleSAML/Auth/ProcessingChain.php       |  11 +-
 lib/SimpleSAML/Auth/Simple.php                |   2 +-
 lib/SimpleSAML/Auth/State.php                 |   8 +-
 lib/SimpleSAML/IdP.php                        |   2 +-
 lib/SimpleSAML/IdP/LogoutTraditional.php      |   6 +
 lib/SimpleSAML/Utilities.php                  | 244 ++++++++++--------
 lib/SimpleSAML/XHTML/IdPDisco.php             |   8 +-
 modules/InfoCard/lib/Auth/Source/ICAuth.php   |  13 +-
 modules/aselect/www/credentials.php           |  13 +-
 .../authYubiKey/lib/Auth/Source/YubiKey.php   |   6 +
 modules/authfacebook/www/linkback.php         |   7 +
 modules/authlinkedin/www/linkback.php         |   6 +
 modules/authmyspace/www/linkback.php          |   6 +
 modules/authorize/www/authorize_403.php       |   7 +
 modules/authtwitter/www/linkback.php          |   6 +
 modules/authwindowslive/www/linkback.php      |   7 +
 modules/cas/www/linkback.php                  |   6 +
 modules/casserver/www/login.php               |   4 +-
 modules/cdc/lib/Server.php                    |   2 +-
 modules/cdc/www/resume.php                    |   6 +
 modules/consent/www/getconsent.php            |   7 +
 modules/consent/www/logout.php                |   7 +
 modules/consent/www/noconsent.php             |   7 +
 modules/core/lib/Auth/UserPassBase.php        |   6 +
 modules/core/lib/Auth/UserPassOrgBase.php     |  12 +
 modules/core/www/as_login.php                 |   6 +-
 modules/core/www/as_logout.php                |   2 +-
 modules/core/www/bwc_resumeauth.php           |   2 +-
 modules/core/www/cleardiscochoices.php        |   4 +-
 modules/core/www/idp/logout-iframe-done.php   |   6 +
 modules/core/www/idp/logout-iframe.php        |   6 +
 modules/core/www/idp/resumelogout.php         |   6 +
 modules/core/www/login-admin.php              |   3 +-
 modules/core/www/loginuserpass.php            |   6 +
 modules/core/www/loginuserpassorg.php         |   6 +
 modules/core/www/short_sso_interval.php       |   7 +
 modules/discopower/lib/PowerIdPDisco.php      |   4 +-
 .../exampleauth/lib/Auth/Source/External.php  |   6 +
 modules/exampleauth/www/authpage.php          |  12 +-
 modules/exampleauth/www/redirecttest.php      |   7 +
 modules/expirycheck/www/about2expire.php      |   7 +
 modules/expirycheck/www/expired.php           |   7 +
 modules/multiauth/www/selectsource.php        |   6 +
 modules/negotiate/www/backend.php             |   7 +
 modules/negotiate/www/retry.php               |   7 +
 modules/oauth/www/authorize.php               |   2 +-
 modules/openid/www/consumer.php               |   7 +
 modules/openid/www/linkback.php               |   7 +
 modules/openidProvider/lib/Server.php         |   6 +
 modules/papi/lib/Auth/Source/PAPI.php         |  14 +
 modules/preprodwarning/www/showwarning.php    |   7 +
 modules/saml/www/sp/discoresp.php             |  10 +-
 modules/saml/www/sp/saml1-acs.php             |  16 +-
 modules/saml/www/sp/saml2-acs.php             |   9 +-
 modules/saml/www/sp/saml2-logout.php          |   6 +
 www/auth/login-admin.php                      |   7 +-
 www/auth/login-cas-ldap.php                   |  15 +-
 www/auth/login-ldapmulti.php                  |  21 +-
 www/auth/login-radius.php                     |  21 +-
 www/auth/login-tlsclient.php                  |  15 +-
 www/auth/login-wayf-ldap.php                  |   4 +-
 www/saml2/idp/SingleLogoutService.php         |   2 +-
 .../idp/idpInitSingleLogoutServiceiFrame.php  |   2 +-
 www/saml2/idp/initSLO.php                     |   3 +-
 www/saml2/sp/AssertionConsumerService.php     |  11 +-
 www/saml2/sp/SingleLogoutService.php          |   4 +-
 www/saml2/sp/initSLO.php                      |   8 +-
 www/saml2/sp/initSSO.php                      |   7 +-
 www/shib13/sp/AssertionConsumerService.php    |   9 +-
 www/shib13/sp/initSSO.php                     |   2 +-
 www/wsfed/sp/initSLO.php                      |   4 +-
 www/wsfed/sp/initSSO.php                      |   2 +-
 76 files changed, 575 insertions(+), 266 deletions(-)

diff --git a/config-templates/config.php b/config-templates/config.php
index 66d1cf49e..989273329 100644
--- a/config-templates/config.php
+++ b/config-templates/config.php
@@ -701,13 +701,26 @@ $config = array (
 	'proxy' => NULL,
 
 	/*
-	 * Array of URL's to allow a trusted redirect to.
+	 * Array of domains that are allowed when generating links or redirections
+	 * to URLs. simpleSAMLphp will use this option to determine whether to
+	 * to consider a given URL valid or not, but you should always validate
+	 * URLs obtained from the input on your own (i.e. ReturnTo or RelayState
+	 * parameters obtained from the $_REQUEST array).
 	 *
-	 * Set to NULL to disable.
+	 * Set to NULL to disable checking of URLs.
+	 *
+	 * simpleSAMLphp will automatically add your own domain (either by checking
+	 * it dinamically, or by using the domain defined in the 'baseurlpath'
+	 * directive, the latter having precedence) to the list of trusted domains,
+	 * in case this option is NOT set to NULL. In that case, you are explicitly
+	 * telling simpleSAMLphp to verify URLs.
+	 *
+	 * Set to an empty array to disallow ALL redirections or links pointing to
+	 * an external URL other than your own domain.
 	 *
 	 * Example:
-	 *   'redirect.trustedsites' => array('sp.example.com', 'othersite.org'),
+	 *   'trusted.url.domains' => array('sp.example.com', 'app.example.com'),
 	 */
-	'redirect.trustedsites' => NULL,
+	'trusted.url.domains' => NULL,
 
 );
diff --git a/docs/simplesamlphp-sp-migration.txt b/docs/simplesamlphp-sp-migration.txt
index 72a165e71..7b425e0e0 100644
--- a/docs/simplesamlphp-sp-migration.txt
+++ b/docs/simplesamlphp-sp-migration.txt
@@ -209,6 +209,9 @@ If you want to return to a specific URL after logging out, you should include th
 
     $as->logout('https://example.org/');
 
+Please make sure the URL is trusted. If you obtain the URL from the user input, make sure it is trusted before
+calling $as->logout(), by using the SimpleSAML_Utilities::checkURLAllowed() method.
+
 
 #### Login link
 
diff --git a/lib/SimpleSAML/Auth/BWC.php b/lib/SimpleSAML/Auth/BWC.php
index 290c514b5..923e9789a 100644
--- a/lib/SimpleSAML/Auth/BWC.php
+++ b/lib/SimpleSAML/Auth/BWC.php
@@ -143,19 +143,19 @@ class SimpleSAML_Auth_BWC extends SimpleSAML_Auth_Simple {
 		$session = SimpleSAML_Session::getInstance();
 		if (!$session->isValid($this->authority)) {
 			/* Not authenticated to this authentication source. */
-			SimpleSAML_Utilities::redirectUntrustedURL($url);
+			SimpleSAML_Utilities::redirectTrustedURL($url);
 			assert('FALSE');
 		}
 
 		if ($this->authority === 'saml2') {
 			$config = SimpleSAML_Configuration::getInstance();
-			SimpleSAML_Utilities::redirectUntrustedURL('/' . $config->getBaseURL() . 'saml2/sp/initSLO.php',
+			SimpleSAML_Utilities::redirectTrustedURL('/' . $config->getBaseURL() . 'saml2/sp/initSLO.php',
 				array('RelayState' => $url)
 			);
 		}
 
 		$session->doLogout($this->authority);
-		SimpleSAML_Utilities::redirectUntrustedURL($url);
+		SimpleSAML_Utilities::redirectTrustedURL($url);
 	}
 
 }
diff --git a/lib/SimpleSAML/Auth/Default.php b/lib/SimpleSAML/Auth/Default.php
index 34686aa2d..ca02e748e 100644
--- a/lib/SimpleSAML/Auth/Default.php
+++ b/lib/SimpleSAML/Auth/Default.php
@@ -19,13 +19,21 @@ class SimpleSAML_Auth_Default {
 	 * This function never returns.
 	 *
 	 * @param string $authId  The identifier of the authentication source.
-	 * @param string|array $return  The URL or function we should direct the user to after authentication.
-	 * @param string|NULL $errorURL  The URL we should direct the user to after failed authentication.
-	 *                               Can be NULL, in which case a standard error page will be shown.
-	 * @param array $params  Extra information about the login. Different authentication requestors may
-	 *                       provide different information. Optional, will default to an empty array.
+	 * @param string|array $return The URL or function we should direct the
+	 * user to after authentication. If using a URL obtained from user input,
+	 * please make sure to check it by calling
+	 * SimpleSAML_Utilities::checkURLAllowed().
+	 * @param string|NULL $errorURL The URL we should direct the user to after
+	 * failed authentication. Can be NULL, in which case a standard error page
+	 * will be shown. If using a URL obtained from user input, please make sure
+	 * to check it by calling SimpleSAML_Utilities::checkURLAllowed().
+	 * @param array $params Extra information about the login. Different
+	 * authentication requestors may provide different information. Optional,
+	 * will default to an empty array.
 	 */
-	public static function initLogin($authId, $return, $errorURL = NULL, array $params = array()) {
+	public static function initLogin($authId, $return, $errorURL = NULL,
+		array $params = array()) {
+
 		assert('is_string($authId)');
 		assert('is_string($return) || is_array($return)');
 		assert('is_string($errorURL) || is_null($errorURL)');
@@ -121,7 +129,7 @@ class SimpleSAML_Auth_Default {
 
 		if (is_string($return)) {
 			/* Redirect... */
-			SimpleSAML_Utilities::redirectUntrustedURL($return);
+			SimpleSAML_Utilities::redirectTrustedURL($return);
 		} else {
 			call_user_func($return, $state);
 			assert('FALSE');
@@ -132,11 +140,16 @@ class SimpleSAML_Auth_Default {
 	/**
 	 * Start logout.
 	 *
-	 * This function starts a logout operation from the current authentication source. This function
-	 * will return if the logout operation does not require a redirect.
+	 * This function starts a logout operation from the current authentication
+	 * source. This function will return if the logout operation does not
+	 * require a redirect.
 	 *
-	 * @param string $returnURL  The URL we should redirect the user to after logging out.
-	 * @param string|NULL $authority  The authentication source we are logging out from, or NULL to log out of the most recent.
+	 * @param string $returnURL The URL we should redirect the user to after
+	 * logging out. No checking is performed on the URL, so make sure to verify
+	 * it on beforehand if the URL is obtained from user input. Refer to
+	 * SimpleSAML_Utilities::checkURLAllowed() for more information.
+	 * @param string|NULL $authority The authentication source we are logging
+	 * out from, or NULL to log out from the most recent.
 	 */
 	public static function initLogoutReturn($returnURL, $authority = NULL) {
 		assert('is_string($returnURL)');
@@ -171,11 +184,16 @@ class SimpleSAML_Auth_Default {
 	/**
 	 * Start logout.
 	 *
-	 * This function starts a logout operation from the current authentication source. This function
-	 * never returns.
+	 * This function starts a logout operation from the current authentication
+	 * source. This function never returns.
 	 *
-	 * @param string $returnURL  The URL we should redirect the user to after logging out.
-	 * @param string|NULL $authority  The authentication source we are logging out from, or NULL to log out of the most recent.
+	 * @param string $returnURL The URL we should redirect the user to after
+	 * logging out. No checking is performed on the URL, so make sure to verify
+	 * it on beforehand if the URL is obtained from user input. Refer to
+	 * SimpleSAML_Utilities::checkURLAllowed() for more information.
+	 * @param string|NULL $authority The authentication source we are logging
+	 * out from, or NULL to log out from the most recent.
+	 * @return void This function never returns.
 	 */
 	public static function initLogout($returnURL, $authority = NULL) {
 		assert('is_string($returnURL)');
@@ -184,7 +202,7 @@ class SimpleSAML_Auth_Default {
 		self::initLogoutReturn($returnURL, $authority);
 
 		/* Redirect... */
-		SimpleSAML_Utilities::redirectUntrustedURL($returnURL);
+		SimpleSAML_Utilities::redirectTrustedURL($returnURL);
 	}
 
 
@@ -202,7 +220,7 @@ class SimpleSAML_Auth_Default {
 		$returnURL = $state['SimpleSAML_Auth_Default.ReturnURL'];
 
 		/* Redirect... */
-		SimpleSAML_Utilities::redirectUntrustedURL($returnURL);
+		SimpleSAML_Utilities::redirectTrustedURL($returnURL);
 	}
 
 
@@ -239,10 +257,14 @@ class SimpleSAML_Auth_Default {
 	 *
 	 * This is used to handle IdP initiated SSO.
 	 *
-	 * @param string $authId  The id of the authentication source that received the request.
-	 * @param array $state  A state array.
-	 * @param string $redirectTo  The URL we should redirect the user to after
-	 *                            updating the session.
+	 * @param string $authId The id of the authentication source that received
+	 * the request.
+	 * @param array $state A state array.
+	 * @param string $redirectTo The URL we should redirect the user to after
+	 * updating the session. The function will check if the URL is allowed, so
+	 * there is no need to manually check the URL on beforehand. Please refer
+	 * to the 'trusted.url.domains' configuration directive for more
+	 * information about allowing (or disallowing) URLs.
 	 */
 	public static function handleUnsolicitedAuth($authId, array $state, $redirectTo) {
 		assert('is_string($authId)');
diff --git a/lib/SimpleSAML/Auth/ProcessingChain.php b/lib/SimpleSAML/Auth/ProcessingChain.php
index da0453264..1b60e3326 100644
--- a/lib/SimpleSAML/Auth/ProcessingChain.php
+++ b/lib/SimpleSAML/Auth/ProcessingChain.php
@@ -248,7 +248,7 @@ class SimpleSAML_Auth_ProcessingChain {
 			 * in $state['ReturnURL'].
 			 */
 			$id = SimpleSAML_Auth_State::saveState($state, self::COMPLETED_STAGE);
-			SimpleSAML_Utilities::redirectUntrustedURL($state['ReturnURL'], array(self::AUTHPARAM => $id));
+			SimpleSAML_Utilities::redirectTrustedURL($state['ReturnURL'], array(self::AUTHPARAM => $id));
 		} else {
 			/* Pass the state to the function defined in $state['ReturnCall']. */
 
@@ -302,8 +302,11 @@ class SimpleSAML_Auth_ProcessingChain {
 	/**
 	 * Retrieve a state which has finished processing.
 	 *
-	 * @param string $id  The identifier of the state. This can be found in the request parameter
-	 *                    with index from SimpleSAML_Auth_ProcessingChain::AUTHPARAM.
+	 * @param string $id The state identifier. This can be found in the
+	 * SimpleSAML_Auth_ProcessingChain::AUTHPARAM request parameter. Please
+	 * make sure to sanitize it properly by calling the
+	 * SimpleSAML_Utilities::checkURLAllowed() function with the embedded
+	 * restart URL, if any. See also SimpleSAML_Utilities::getURLFromStateID().
 	 */
 	public static function fetchProcessedState($id) {
 		assert('is_string($id)');
@@ -351,5 +354,3 @@ class SimpleSAML_Auth_ProcessingChain {
 	}
 
 }
-
-?>
\ No newline at end of file
diff --git a/lib/SimpleSAML/Auth/Simple.php b/lib/SimpleSAML/Auth/Simple.php
index 8577379e8..4f7d8fb05 100644
--- a/lib/SimpleSAML/Auth/Simple.php
+++ b/lib/SimpleSAML/Auth/Simple.php
@@ -219,7 +219,7 @@ class SimpleSAML_Auth_Simple {
 				$params[$state['ReturnStateParam']] = $stateID;
 			}
 
-			SimpleSAML_Utilities::redirectUntrustedURL($state['ReturnTo'], $params);
+			SimpleSAML_Utilities::redirectTrustedURL($state['ReturnTo'], $params);
 		}
 	}
 
diff --git a/lib/SimpleSAML/Auth/State.php b/lib/SimpleSAML/Auth/State.php
index 57387665d..0b74da24c 100644
--- a/lib/SimpleSAML/Auth/State.php
+++ b/lib/SimpleSAML/Auth/State.php
@@ -211,13 +211,7 @@ class SimpleSAML_Auth_State {
 		assert('is_bool($allowMissing)');
 		SimpleSAML_Logger::debug('Loading state: ' . var_export($id, TRUE));
 
-		$tmp = explode(':', $id, 2);
-		$id = $tmp[0];
-		if (count($tmp) === 2) {
-			$restartURL = $tmp[1];
-		} else {
-			$restartURL = NULL;
-		}
+		$restartURL = SimpleSAML_Utilities::getURLFromStateID($id);
 
 		$session = SimpleSAML_Session::getInstance();
 		$state = $session->getData('SimpleSAML_Auth_State', $id);
diff --git a/lib/SimpleSAML/IdP.php b/lib/SimpleSAML/IdP.php
index 630662dd7..d94c4274a 100644
--- a/lib/SimpleSAML/IdP.php
+++ b/lib/SimpleSAML/IdP.php
@@ -529,7 +529,7 @@ class SimpleSAML_IdP {
 	public static function finishLogoutRedirect(SimpleSAML_IdP $idp, array $state) {
 		assert('isset($state["core:Logout:URL"])');
 
-		SimpleSAML_Utilities::redirectUntrustedURL($state['core:Logout:URL']);
+		SimpleSAML_Utilities::redirectTrustedURL($state['core:Logout:URL']);
 		assert('FALSE');
 	}
 
diff --git a/lib/SimpleSAML/IdP/LogoutTraditional.php b/lib/SimpleSAML/IdP/LogoutTraditional.php
index 86ce301e9..f9fa132f7 100644
--- a/lib/SimpleSAML/IdP/LogoutTraditional.php
+++ b/lib/SimpleSAML/IdP/LogoutTraditional.php
@@ -76,6 +76,12 @@ class SimpleSAML_IdP_LogoutTraditional extends SimpleSAML_IdP_LogoutHandler {
 			throw new SimpleSAML_Error_Exception('RelayState lost during logout.');
 		}
 
+		// sanitize the input
+		$restartURL = SimpleSAML_Utilities::getURLFromStateID($relayState);
+		if (!is_null($restartURL)) {
+			SimpleSAML_Utilities::checkURLAllowed($restartURL);
+		}
+
 		$state = SimpleSAML_Auth_State::loadState($relayState, 'core:LogoutTraditional');
 
 		if ($error === NULL) {
diff --git a/lib/SimpleSAML/Utilities.php b/lib/SimpleSAML/Utilities.php
index 0b37bf839..cd7ef1f28 100644
--- a/lib/SimpleSAML/Utilities.php
+++ b/lib/SimpleSAML/Utilities.php
@@ -298,17 +298,80 @@ class SimpleSAML_Utilities {
 	}
 
 
+	/**
+	 * Check if a URL is valid and is in our list of allowed URLs.
+	 *
+	 * @param string $url The URL to check.
+	 * @param array $trustedSites An optional white list of domains. If none
+	 * specified, the 'trusted.url.domains' configuration directive will be
+	 * used.
+	 * @return string The normalized URL itself if it is allowed.
+	 * @throws SimpleSAML_Error_Exception if the URL is malformed or is not
+	 * allowed by configuration.
+	 */
+	public static function checkURLAllowed($url, array $trustedSites = NULL) {
+		$url = self::normalizeURL($url);
+
+		// verify that the URL points to an http or https site
+		if (!preg_match('@^https?://@i', $url)) {
+			throw new SimpleSAML_Error_Exception('Invalid URL: '.$url);
+		}
+
+		// get the white list of domains
+		if ($trustedSites === NULL) {
+			$trustedSites = SimpleSAML_Configuration::getInstance()->getArray('trusted.url.domains', NULL);
+			if ($trustedSites === NULL) {
+				$trustedSites = SimpleSAML_Configuration::getInstance()->getArray('redirect.trustedsites', NULL);
+			}
+		}
+
+		// validates the URL's host is among those allowed
+		if ($trustedSites !== NULL) {
+			assert(is_array($trustedSites));
+			preg_match('@^https?://([^/]+)@i', $url, $matches);
+			$hostname = $matches[1];
+
+			// add self host to the white list
+			$self_host = self::getSelfHost();
+			$trustedSites[] = $self_host;
+
+			/* Throw exception due to redirection to untrusted site */
+			if (!in_array($hostname, $trustedSites)) {
+				throw new SimpleSAML_Error_Exception('URL not allowed: '.$url);
+			}
+		}
+		return $url;
+	}
+
+
+	/**
+	 * Get a URL embedded in a StateID, in the form 'id:url'.
+	 *
+	 * @param string $stateId The state ID to use.
+	 * @return string The embedded URL if found, NULL otherwise.
+	 */
+	public static function getURLFromStateID($stateId) {
+		$tmp = explode(':', $stateId, 2);
+		$id = $tmp[0];
+		$url = NULL;
+		if (count($tmp) === 2) {
+			$url = $tmp[1];
+		}
+		return $url;
+	}
+
+
 	public static function checkDateConditions($start=NULL, $end=NULL) {
 		$currentTime = time();
 	
-		if (! empty($start)) {
+		if (!empty($start)) {
 			$startTime = SAML2_Utils::parseSAML2Time($start);
 			/* Allow for a 10 minute difference in Time */
 			if (($startTime < 0) || (($startTime - 600) > $currentTime)) {
 				return FALSE;
 			}
 		}
-		if (! empty($end)) {
+		if (!empty($end)) {
 			$endTime = SAML2_Utils::parseSAML2Time($end);
 			if (($endTime < 0) || ($endTime <= $currentTime)) {
 				return FALSE;
@@ -493,113 +556,20 @@ class SimpleSAML_Utilities {
 		return true;
 	}
 
-
-	/**
-	 * This function redirects the user to the specified address.
-	 *
-	 * This function will use the "HTTP 303 See Other" redirection if the
-	 * current request used the POST method and the HTTP version is 1.1.
-	 * Otherwise, a "HTTP 302 Found" redirection will be used.
-	 *
-	 * The fuction will also generate a simple web page with a clickable
-	 * link to the target page.
-	 *
-	 * @param string $url The URL we should redirect to. This URL may include
-	 * query parameters. If this URL is a relative URL (starting with '/'),
-	 * then it will be turned into an absolute URL by prefixing it with the
-	 * absolute URL to the root of the website.
-	 * @param string[] $parameters An array with extra query string parameters
-	 * which should be appended to the URL. The name of the parameter is the
-	 * array index. The value of the parameter is the value stored in the index.
-	 * Both the name and the value will be urlencoded. If the value is NULL,
-	 * then the parameter will be encoded as just the name, without a value.
-	 * @param string[] $allowed_redirect_hosts An array with a whitelist of
-	 * hosts for which redirects are allowed. If NULL, redirections will be
-	 * allowed to any host. Otherwise, the host of the $url provided must be
-	 * present in this parameter. If the host is not whitelisted, an exception
-	 * will be thrown.
-	 *
-	 * @return void This function never returns.
-	 * @deprecated 1.12.0 This function will be removed from the API. Use
-	 * accordingly the redirectTrustedURL or redirectUntrustedURL functions
-	 * instead.
+	/*
+	 * This is a temporary function, holding the redirect() functionality,
+	 * meanwhile we are deprecating the it.
 	 */
-	public static function redirect($url, $parameters = array(), $allowed_redirect_hosts = NULL) {
-		assert(is_string($url));
-		assert(strlen($url) > 0);
-		assert(is_array($parameters));
-		if($allowed_redirect_hosts != NULL) assert(is_array($allowed_redirect_hosts));
-
-		/* Check for relative URL. */
-		if(substr($url, 0, 1) === '/') {
-			/* Prefix the URL with the url to the root of the
-			 * website.
-			 */
-			$url = self::selfURLhost() . $url;
+	private static function _doRedirect($url, $parameters = array()) {
+		if (!empty($parameters)) {
+			$url = self::addURLparameter($url, $parameters);
 		}
 
-		/* Verify that the URL points to an http or https site. */
-		if (!preg_match('@^https?://@i', $url)) {
-			throw new SimpleSAML_Error_Exception('Redirect to invalid URL: ' . $url);
-		}
-
-		/* Validates the URL's host is among those allowed. */
-		if ($allowed_redirect_hosts !== NULL) {
-			preg_match('@^https?://([^/]+)@i', $url, $matches);
-			$hostname = $matches[1];
-
-			// add self host to the white list
-			$self_host = self::getSelfHost();
-			$allowed_redirect_hosts[] = $self_host;
-
-			/* Throw exception due to redirection to untrusted site */
-			if(!in_array($hostname, $allowed_redirect_hosts)) {
-				throw new SimpleSAML_Error_Exception('Redirection not to allowed to URL: ' . $url);
-			}
-		}
-
-		/* Determine which prefix we should put before the first
-		 * parameter.
-		 */
-		if(strpos($url, '?') === FALSE) {
-			$paramPrefix = '?';
-		} else {
-			$paramPrefix = '&';
-		}
-
-		/* Iterate over the parameters and append them to the query
-		 * string.
-		 */
-		foreach($parameters as $name => $value) {
-
-			/* Encode the parameter. */
-			if($value === NULL) {
-				$param = urlencode($name);
-			} elseif (is_array($value)) {
-				$param = "";
-				foreach ($value as $val) {
-					$param .= urlencode($name) . "[]=" . urlencode($val) . '&';				
-				}
-			} else {
-				$param = urlencode($name) . '=' .
-					urlencode($value);
-			}
-
-			/* Append the parameter to the query string. */
-			$url .= $paramPrefix . $param;
-
-			/* Every following parameter is guaranteed to follow
-			 * another parameter. Therefore we use the '&' prefix.
-			 */
-			$paramPrefix = '&';
-		}
-
-
 		/* Set the HTTP result code. This is either 303 See Other or
 		 * 302 Found. HTTP 303 See Other is sent if the HTTP version
 		 * is HTTP/1.1 and the request type was a POST request.
 		 */
-		if($_SERVER['SERVER_PROTOCOL'] === 'HTTP/1.1' &&
+		if ($_SERVER['SERVER_PROTOCOL'] === 'HTTP/1.1' &&
 			$_SERVER['REQUEST_METHOD'] === 'POST') {
 			$code = 303;
 		} else {
@@ -630,7 +600,8 @@ class SimpleSAML_Utilities {
 		echo '<h1>Redirect</h1>';
 		echo '<p>';
 		echo 'You were redirected to: ';
-		echo '<a id="redirlink" href="' . htmlspecialchars($url) . '">' . htmlspecialchars($url) . '</a>';
+		echo '<a id="redirlink" href="' .
+			htmlspecialchars($url) . '">' . htmlspecialchars($url) . '</a>';
 		echo '<script type="text/javascript">document.getElementById("redirlink").focus();</script>';
 		echo '</p>';
 		echo '</body>';
@@ -638,6 +609,51 @@ class SimpleSAML_Utilities {
 
 		/* End script execution. */
 		exit;
+	} 
+
+
+	/**
+	 * This function redirects the user to the specified address.
+	 *
+	 * This function will use the "HTTP 303 See Other" redirection if the
+	 * current request used the POST method and the HTTP version is 1.1.
+	 * Otherwise, a "HTTP 302 Found" redirection will be used.
+	 *
+	 * The fuction will also generate a simple web page with a clickable
+	 * link to the target page.
+	 *
+	 * @param string $url The URL we should redirect to. This URL may include
+	 * query parameters. If this URL is a relative URL (starting with '/'),
+	 * then it will be turned into an absolute URL by prefixing it with the
+	 * absolute URL to the root of the website.
+	 * @param string[] $parameters An array with extra query string parameters
+	 * which should be appended to the URL. The name of the parameter is the
+	 * array index. The value of the parameter is the value stored in the index.
+	 * Both the name and the value will be urlencoded. If the value is NULL,
+	 * then the parameter will be encoded as just the name, without a value.
+ 	 * @param string[] $allowed_redirect_hosts An array with a whitelist of
+	 * hosts for which redirects are allowed. If NULL, redirections will be
+	 * allowed to any host. Otherwise, the host of the $url provided must be
+	 * present in this parameter. If the host is not whitelisted, an exception
+	 * will be thrown.
+	 *
+	 * @return void This function never returns.
+	 * @deprecated 1.12.0 This function will be removed from the API. Instead,
+	 * use the redirectTrustedURL or redirectUntrustedURL functions
+	 * accordingly.
+	 */
+	public static function redirect($url, $parameters = array(),
+		$allowed_redirect_hosts = NULL) {
+		
+		assert(is_string($url));
+		assert(strlen($url) > 0);
+		assert(is_array($parameters));
+
+		$url = self::normalizeURL($url);
+		if ($allowed_redirect_hosts !== NULL) {
+			$url = self::checkURLAllowed($url, $allowed_redirect_hosts);	
+		}
+		self::_doRedirect($url, $parameters);
 	}
 
 	/**
@@ -665,26 +681,27 @@ class SimpleSAML_Utilities {
 	 * @return void This function never returns.
 	 */
 	public static function redirectTrustedURL($url, $parameters = array()) {
-		self::redirect($url, $parameters);
+		$url = self::normalizeURL($url);
+		self::_doRedirect($url, $parameters);
 	}
 
 	/**
 	 * This function redirects to the specified URL after performing the
-	 * appropriate security checks on it. Particularly, it will make sure
-	 * that the provided URL is allowed by the 'redirect.trustedsites'
-	 * directive in the configuration.
+	 * appropriate security checks on it. Particularly, it will make sure that
+	 * the provided URL is allowed by the 'redirect.trustedsites' directive
+	 * in the configuration.
 	 *
-	 * If the aforementioned option is not set or the URL does corresponds
-	 * to a trusted site, it performs a redirection to it. If the site is
-	 * not trusted, an exception will be thrown.
+	 * If the aforementioned option is not set or the URL does correspond to a
+	 * trusted site, it performs a redirection to it. If the site is not
+	 * trusted, an exception will be thrown.
 	 *
 	 * See the redirectTrustedURL function for more details.
 	 * 
 	 * @return void This function never returns.
 	 */
 	public static function redirectUntrustedURL($url, $parameters = array()) {
-		$trustedSites = SimpleSAML_Configuration::getInstance()->getArray('redirect.trustedsites', NULL);
-		self::redirect($url, $parameters, $trustedSites);
+		$url = self::checkURLAllowed($url);
+		self::_doRedirect($url, $parameters);
 	}
 
 	/**
@@ -1150,8 +1167,7 @@ class SimpleSAML_Utilities {
 			$base = self::getBaseURL();
 		}
 
-
-		if(!preg_match('$^((((\w+:)//[^/]+)(/[^?#]*))(?:\?[^#]*)?)(?:#.*)?$', $base, $baseParsed)) {
+		if(!preg_match('/^((((\w+:)\/\/[^\/]+)(\/[^?#]*))(?:\?[^#]*)?)(?:#.*)?/', $base, $baseParsed)) {
 			throw new Exception('Unable to parse base url: ' . $base);
 		}
 
diff --git a/lib/SimpleSAML/XHTML/IdPDisco.php b/lib/SimpleSAML/XHTML/IdPDisco.php
index c1c5be0c4..7e7eeb938 100644
--- a/lib/SimpleSAML/XHTML/IdPDisco.php
+++ b/lib/SimpleSAML/XHTML/IdPDisco.php
@@ -124,7 +124,7 @@ class SimpleSAML_XHTML_IdPDisco {
 		if(!array_key_exists('return', $_GET)) {
 			throw new Exception('Missing parameter: return');
 		} else {
-			$this->returnURL = $_GET['return'];
+			$this->returnURL = SimpleSAML_Utilities::checkURLAllowed($_GET['return']);
 		}
 		
 		$this->isPassive = FALSE;
@@ -474,7 +474,7 @@ class SimpleSAML_XHTML_IdPDisco {
 				
 			} else {
 				$this->log('Choice made [' . $idp . '] (Redirecting the user back. returnIDParam=' . $this->returnIdParam . ')');
-				SimpleSAML_Utilities::redirectUntrustedURL($this->returnURL, array($this->returnIdParam => $idp));
+				SimpleSAML_Utilities::redirectTrustedURL($this->returnURL, array($this->returnIdParam => $idp));
 			}
 			
 			return;
@@ -482,7 +482,7 @@ class SimpleSAML_XHTML_IdPDisco {
 		
 		if ($this->isPassive) {
 			$this->log('Choice not made. (Redirecting the user back without answer)');
-			SimpleSAML_Utilities::redirectUntrustedURL($this->returnURL);
+			SimpleSAML_Utilities::redirectTrustedURL($this->returnURL);
 			return;
 		}
 
@@ -500,7 +500,7 @@ class SimpleSAML_XHTML_IdPDisco {
         
         if(sizeof($idpintersection)  == 1) {
             $this->log('Choice made [' . $idpintersection[0] . '] (Redirecting the user back. returnIDParam=' . $this->returnIdParam . ')');
-            SimpleSAML_Utilities::redirectUntrustedURL($this->returnURL, array($this->returnIdParam => $idpintersection[0]));
+            SimpleSAML_Utilities::redirectTrustedURL($this->returnURL, array($this->returnIdParam => $idpintersection[0]));
         }
 
 		/*
diff --git a/modules/InfoCard/lib/Auth/Source/ICAuth.php b/modules/InfoCard/lib/Auth/Source/ICAuth.php
index 39e746c56..0b76ed1c0 100644
--- a/modules/InfoCard/lib/Auth/Source/ICAuth.php
+++ b/modules/InfoCard/lib/Auth/Source/ICAuth.php
@@ -42,7 +42,6 @@ class sspmod_InfoCard_Auth_Source_ICAuth extends SimpleSAML_Auth_Source {
 	
 
 	public static function handleLogin($authStateId, $xmlToken) {
-SimpleSAML_Logger::debug('ENTRA en icauth');
 		assert('is_string($authStateId)');		
 
 		$config = SimpleSAML_Configuration::getInstance();
@@ -61,14 +60,20 @@ SimpleSAML_Logger::debug('ENTRA en icauth');
     	SimpleSAML_Logger::debug("NOXMLtoken: ".$xmlToken);
 		$claims = $infocard->process($xmlToken);
  		if($claims->isValid()) {
-//		if(false) {
 			$attributes = array();
 			foreach ($Infocard['requiredClaims'] as $claim => $data){
 				$attributes[$claim] = array($claims->$claim);
 			}
 			foreach ($Infocard['optionalClaims'] as $claim => $data){
 				$attributes[$claim] = array($claims->$claim);
-			}	
+			}
+
+			// sanitize the input
+			$restartURL = SimpleSAML_Utilities::getURLFromStateID($authStateId);
+			if (!is_null($restartURL)) {
+				SimpleSAML_Utilities::checkURLAllowed($restartURL);
+			}
+
 			/* Retrieve the authentication state. */
 			$state = SimpleSAML_Auth_State::loadState($authStateId, self::STAGEID);
 			/* Find authentication source. */
@@ -78,12 +83,10 @@ SimpleSAML_Logger::debug('ENTRA en icauth');
 				throw new Exception('Could not find authentication source with id ' . $state[self::AUTHID]);
 			}			
 			$state['Attributes'] = $attributes;	
-SimpleSAML_Logger::debug('VALIDA');
 			unset($infocard);
 			unset($claims);
 			SimpleSAML_Auth_Source::completeAuth($state);
 		} else {
-SimpleSAML_Logger::debug('NO VALIDA ERROR:'.$claims->getErrorMsg());
 			unset($infocard);
 			unset($claims);
 			return 'wrong_IC';
diff --git a/modules/aselect/www/credentials.php b/modules/aselect/www/credentials.php
index 3d3b8cba1..dc0afe7e4 100644
--- a/modules/aselect/www/credentials.php
+++ b/modules/aselect/www/credentials.php
@@ -7,7 +7,18 @@
  * @author Wessel Dankers, Tilburg University
  */
 function check_credentials() {
-	$state = SimpleSAML_Auth_State::loadState($_REQUEST['ssp_state'], 'aselect:login');
+	
+	if (!array_key_exists('ssp_state', $_REQUEST))
+		SimpleSAML_Auth_State::throwException($state, new SimpleSAML_Error_Exception("Missing ssp_state parameter"));	
+	$id = $_REQUEST['ssp_state'];
+
+	// sanitize the input
+	$restartURL = SimpleSAML_Utilities::getURLFromStateID($id);
+	if (!is_null($restartURL)) {
+		SimpleSAML_Utilities::checkURLAllowed($restartURL);
+	}
+
+	$state = SimpleSAML_Auth_State::loadState($id, 'aselect:login');
 
 	if(!array_key_exists('a-select-server', $_REQUEST))
 		SimpleSAML_Auth_State::throwException($state, new SimpleSAML_Error_Exception("Missing a-select-server parameter"));
diff --git a/modules/authYubiKey/lib/Auth/Source/YubiKey.php b/modules/authYubiKey/lib/Auth/Source/YubiKey.php
index ae98920d6..6b1412897 100644
--- a/modules/authYubiKey/lib/Auth/Source/YubiKey.php
+++ b/modules/authYubiKey/lib/Auth/Source/YubiKey.php
@@ -124,6 +124,12 @@ class sspmod_authYubiKey_Auth_Source_YubiKey extends SimpleSAML_Auth_Source {
 		assert('is_string($authStateId)');
 		assert('is_string($otp)');
 
+		// sanitize the input
+		$restartURL = SimpleSAML_Utilities::getURLFromStateID($authStateId);
+		if (!is_null($restartURL)) {
+			SimpleSAML_Utilities::checkURLAllowed($restartURL);
+		}
+
 		/* Retrieve the authentication state. */
 		$state = SimpleSAML_Auth_State::loadState($authStateId, self::STAGEID);
 
diff --git a/modules/authfacebook/www/linkback.php b/modules/authfacebook/www/linkback.php
index 3a27fe5ea..2305f773d 100644
--- a/modules/authfacebook/www/linkback.php
+++ b/modules/authfacebook/www/linkback.php
@@ -9,6 +9,13 @@ if (!array_key_exists('AuthState', $_REQUEST) || empty($_REQUEST['AuthState']))
 }
 
 $stateID = $_REQUEST['AuthState'];
+
+// sanitize the input
+$restartURL = SimpleSAML_Utilities::getURLFromStateID($stateID);
+if (!is_null($restartURL)) {
+	SimpleSAML_Utilities::checkURLAllowed($restartURL);
+}
+
 $state = SimpleSAML_Auth_State::loadState($stateID, sspmod_authfacebook_Auth_Source_Facebook::STAGE_INIT);
 
 /* Find authentication source. */
diff --git a/modules/authlinkedin/www/linkback.php b/modules/authlinkedin/www/linkback.php
index 961eaa6bf..a169f04ee 100644
--- a/modules/authlinkedin/www/linkback.php
+++ b/modules/authlinkedin/www/linkback.php
@@ -10,6 +10,12 @@ if (array_key_exists('stateid', $_REQUEST)) {
         throw new Exception('Lost OAuth Client State');
 }
 
+// sanitize the input
+$restartURL = SimpleSAML_Utilities::getURLFromStateID($stateId);
+if (!is_null($restartURL)) {
+	SimpleSAML_Utilities::checkURLAllowed($restartURL);
+}
+
 $state = SimpleSAML_Auth_State::loadState($stateId, sspmod_authlinkedin_Auth_Source_LinkedIn::STAGE_INIT);
 
 // http://developer.linkedin.com/docs/DOC-1008#2_Redirect_the_User_to_our_Authorization_Server
diff --git a/modules/authmyspace/www/linkback.php b/modules/authmyspace/www/linkback.php
index 93c45153c..81683c837 100644
--- a/modules/authmyspace/www/linkback.php
+++ b/modules/authmyspace/www/linkback.php
@@ -10,6 +10,12 @@ if (array_key_exists('stateid', $_REQUEST)) {
 	throw new Exception('State Lost - not returned by MySpace Auth');
 }
 
+// sanitize the input
+$restartURL = SimpleSAML_Utilities::getURLFromStateID($stateId);
+if (!is_null($restartURL)) {
+	SimpleSAML_Utilities::checkURLAllowed($restartURL);
+}
+
 $state = SimpleSAML_Auth_State::loadState($stateId, sspmod_authmyspace_Auth_Source_MySpace::STAGE_INIT);
 
 if (array_key_exists('oauth_problem', $_REQUEST)) {
diff --git a/modules/authorize/www/authorize_403.php b/modules/authorize/www/authorize_403.php
index 54d702fb0..613fa1034 100644
--- a/modules/authorize/www/authorize_403.php
+++ b/modules/authorize/www/authorize_403.php
@@ -11,6 +11,13 @@ if (!array_key_exists('StateId', $_REQUEST)) {
 }
 
 $id = $_REQUEST['StateId'];
+
+// sanitize the input
+$restartURL = SimpleSAML_Utilities::getURLFromStateID($id);
+if (!is_null($restartURL)) {
+	SimpleSAML_Utilities::checkURLAllowed($restartURL);
+}
+
 $state = SimpleSAML_Auth_State::loadState($id, 'authorize:Authorize');
 
 $globalConfig = SimpleSAML_Configuration::getInstance();
diff --git a/modules/authtwitter/www/linkback.php b/modules/authtwitter/www/linkback.php
index de6ec85ce..0ebea758a 100644
--- a/modules/authtwitter/www/linkback.php
+++ b/modules/authtwitter/www/linkback.php
@@ -9,6 +9,12 @@ if (!array_key_exists('AuthState', $_REQUEST) || empty($_REQUEST['AuthState']))
 }
 $stateID = $_REQUEST['AuthState'];
 
+// sanitize the input
+$restartURL = SimpleSAML_Utilities::getURLFromStateID($stateID);
+if (!is_null($restartURL)) {
+	SimpleSAML_Utilities::checkURLAllowed($restartURL);
+}
+
 $state = SimpleSAML_Auth_State::loadState($stateID, sspmod_authtwitter_Auth_Source_Twitter::STAGE_INIT);
 
 /* Find authentication source. */
diff --git a/modules/authwindowslive/www/linkback.php b/modules/authwindowslive/www/linkback.php
index 7ae42661f..ee8452de4 100644
--- a/modules/authwindowslive/www/linkback.php
+++ b/modules/authwindowslive/www/linkback.php
@@ -6,6 +6,13 @@
 
 if (array_key_exists('wrap_client_state', $_REQUEST)) {
 	$stateId = $_REQUEST['wrap_client_state'];
+	
+	// sanitize the input
+	$restartURL = SimpleSAML_Utilities::getURLFromStateID($stateId);
+	if (!is_null($restartURL)) {
+		SimpleSAML_Utilities::checkURLAllowed($restartURL);
+	}
+
 	$state = SimpleSAML_Auth_State::loadState($stateId, sspmod_authwindowslive_Auth_Source_LiveID::STAGE_INIT);
 } else {
 	throw new Exception('Lost OAuth-WRAP Client State');
diff --git a/modules/cas/www/linkback.php b/modules/cas/www/linkback.php
index 1e6740c0b..8fe43e2f5 100644
--- a/modules/cas/www/linkback.php
+++ b/modules/cas/www/linkback.php
@@ -13,6 +13,12 @@ if (!isset($_GET['ticket'])) {
 	throw new SimpleSAML_Error_BadRequest('Missing ticket parameter.');
 }
 
+// sanitize the input
+$restartURL = SimpleSAML_Utilities::getURLFromStateID($stateId);
+if (!is_null($restartURL)) {
+	SimpleSAML_Utilities::checkURLAllowed($restartURL);
+}
+
 $state = SimpleSAML_Auth_State::loadState($stateId, sspmod_cas_Auth_Source_CAS::STAGE_INIT);
 $state['cas:ticket'] = (string)$_GET['ticket'];
 
diff --git a/modules/casserver/www/login.php b/modules/casserver/www/login.php
index e59c33248..0410acdc3 100644
--- a/modules/casserver/www/login.php
+++ b/modules/casserver/www/login.php
@@ -48,10 +48,8 @@ storeTicket($ticket, $path, array('service' => $service,
 	'proxies' => array(),
 	'validbefore' => time() + 5));
 
-SimpleSAML_Utilities::redirectUntrustedURL(
+SimpleSAML_Utilities::redirectTrustedURL(
 	SimpleSAML_Utilities::addURLparameter($service,
 		array('ticket' => $ticket)
 	)
 );
-
-?>
diff --git a/modules/cdc/lib/Server.php b/modules/cdc/lib/Server.php
index 8c962eb19..d4dacf736 100644
--- a/modules/cdc/lib/Server.php
+++ b/modules/cdc/lib/Server.php
@@ -327,7 +327,7 @@ class sspmod_cdc_Server {
 
 		$url = SimpleSAML_Utilities::addURLparameter($to, $params);
 		if (strlen($url) < 2048) {
-			SimpleSAML_Utilities::redirectUntrustedURL($url);
+			SimpleSAML_Utilities::redirectTrustedURL($url);
 		} else {
 			SimpleSAML_Utilities::postRedirect($to, $params);
 		}
diff --git a/modules/cdc/www/resume.php b/modules/cdc/www/resume.php
index 6e4fcf381..549be2288 100644
--- a/modules/cdc/www/resume.php
+++ b/modules/cdc/www/resume.php
@@ -17,6 +17,12 @@ if (!isset($response['id'])) {
 	throw new SimpleSAML_Error_BadRequest('CDCResponse without id.');
 }
 
+// sanitize the input
+$restartURL = SimpleSAML_Utilities::getURLFromStateID($response['id']);
+if (!is_null($restartURL)) {
+	SimpleSAML_Utilities::checkURLAllowed($restartURL);
+}
+
 $state = SimpleSAML_Auth_State::loadState($response['id'], 'cdc:resume');
 
 SimpleSAML_Auth_ProcessingChain::resumeProcessing($state);
diff --git a/modules/consent/www/getconsent.php b/modules/consent/www/getconsent.php
index 3628bd1ab..3e3081702 100644
--- a/modules/consent/www/getconsent.php
+++ b/modules/consent/www/getconsent.php
@@ -31,6 +31,13 @@ if (!array_key_exists('StateId', $_REQUEST)) {
 }
 
 $id = $_REQUEST['StateId'];
+
+// sanitize the input
+$restartURL = SimpleSAML_Utilities::getURLFromStateID($id);
+if (!is_null($restartURL)) {
+	SimpleSAML_Utilities::checkURLAllowed($restartURL);
+}
+
 $state = SimpleSAML_Auth_State::loadState($id, 'consent:request');
 
 if (array_key_exists('core:SP', $state)) {
diff --git a/modules/consent/www/logout.php b/modules/consent/www/logout.php
index 55903707b..1e464294a 100644
--- a/modules/consent/www/logout.php
+++ b/modules/consent/www/logout.php
@@ -10,6 +10,13 @@ if (!array_key_exists('StateId', $_GET)) {
     throw new SimpleSAML_Error_BadRequest('Missing required StateId query parameter.');
 }
 $id = (string)$_GET['StateId'];
+
+// sanitize the input
+$restartURL = SimpleSAML_Utilities::getURLFromStateID($id);
+if (!is_null($restartURL)) {
+	SimpleSAML_Utilities::checkURLAllowed($restartURL);
+}
+
 $state = SimpleSAML_Auth_State::loadState($id, 'consent:request');
 
 $state['Responder'] = array('sspmod_consent_Logout', 'postLogout');
diff --git a/modules/consent/www/noconsent.php b/modules/consent/www/noconsent.php
index 06e5554c0..37b592011 100644
--- a/modules/consent/www/noconsent.php
+++ b/modules/consent/www/noconsent.php
@@ -12,6 +12,13 @@ if (!array_key_exists('StateId', $_REQUEST)) {
 }
 
 $id = $_REQUEST['StateId'];
+
+// sanitize the input
+$restartURL = SimpleSAML_Utilities::getURLFromStateID($id);
+if (!is_null($restartURL)) {
+	SimpleSAML_Utilities::checkURLAllowed($restartURL);
+}
+
 $state = SimpleSAML_Auth_State::loadState($id, 'consent:request');
 
 $resumeFrom = SimpleSAML_Module::getModuleURL(
diff --git a/modules/core/lib/Auth/UserPassBase.php b/modules/core/lib/Auth/UserPassBase.php
index 633a7484a..c7ff0bebe 100644
--- a/modules/core/lib/Auth/UserPassBase.php
+++ b/modules/core/lib/Auth/UserPassBase.php
@@ -197,6 +197,12 @@ abstract class sspmod_core_Auth_UserPassBase extends SimpleSAML_Auth_Source {
 		assert('is_string($username)');
 		assert('is_string($password)');
 
+		// sanitize the input
+		$restartURL = SimpleSAML_Utilities::getURLFromStateID($authStateId);
+		if (!is_null($restartURL)) {
+			SimpleSAML_Utilities::checkURLAllowed($restartURL);
+		}
+
 		/* Here we retrieve the state array we saved in the authenticate-function. */
 		$state = SimpleSAML_Auth_State::loadState($authStateId, self::STAGEID);
 
diff --git a/modules/core/lib/Auth/UserPassOrgBase.php b/modules/core/lib/Auth/UserPassOrgBase.php
index f79c3aefa..9c7af9731 100644
--- a/modules/core/lib/Auth/UserPassOrgBase.php
+++ b/modules/core/lib/Auth/UserPassOrgBase.php
@@ -209,6 +209,12 @@ abstract class sspmod_core_Auth_UserPassOrgBase extends SimpleSAML_Auth_Source {
 		assert('is_string($password)');
 		assert('is_string($organization)');
 
+		// sanitize the input
+		$restartURL = SimpleSAML_Utilities::getURLFromStateID($authStateId);
+		if (!is_null($restartURL)) {
+			SimpleSAML_Utilities::checkURLAllowed($restartURL);
+		}
+
 		/* Retrieve the authentication state. */
 		$state = SimpleSAML_Auth_State::loadState($authStateId, self::STAGEID);
 
@@ -257,6 +263,12 @@ abstract class sspmod_core_Auth_UserPassOrgBase extends SimpleSAML_Auth_Source {
 	public static function listOrganizations($authStateId) {
 		assert('is_string($authStateId)');
 
+		// sanitize the input
+		$restartURL = SimpleSAML_Utilities::getURLFromStateID($authStateId);
+		if (!is_null($restartURL)) {
+			SimpleSAML_Utilities::checkURLAllowed($restartURL);
+		}
+
 		/* Retrieve the authentication state. */
 		$state = SimpleSAML_Auth_State::loadState($authStateId, self::STAGEID);
 
diff --git a/modules/core/www/as_login.php b/modules/core/www/as_login.php
index a30bd1f77..09c07209c 100644
--- a/modules/core/www/as_login.php
+++ b/modules/core/www/as_login.php
@@ -19,7 +19,7 @@ if (!is_string($_REQUEST['AuthId'])) {
  * Setting up the options for the requireAuth() call later..
  */
 $options = array(
-	'ReturnTo' => $_REQUEST['ReturnTo'],
+	'ReturnTo' => SimpleSAML_Utilities::checkURLAllowed($_REQUEST['ReturnTo']),
 );
 
 /*
@@ -30,9 +30,7 @@ if (!empty($_REQUEST['saml:idp'])) {
 	$options['saml:idp'] = $_REQUEST['saml:idp'];
 }
 
-
-
 $as = new SimpleSAML_Auth_Simple($_REQUEST['AuthId']);
 $as->requireAuth($options);
 
-SimpleSAML_Utilities::redirectUntrustedURL($_REQUEST['ReturnTo']);
+SimpleSAML_Utilities::redirectTrustedURL($options['ReturnTo']);
diff --git a/modules/core/www/as_logout.php b/modules/core/www/as_logout.php
index 5ae8a39c3..dad67d213 100644
--- a/modules/core/www/as_logout.php
+++ b/modules/core/www/as_logout.php
@@ -16,4 +16,4 @@ if (!isset($_REQUEST['AuthId']) || !is_string($_REQUEST['AuthId'])) {
 }
 
 $as = new SimpleSAML_Auth_Simple($_REQUEST['AuthId']);
-$as->logout($_REQUEST['ReturnTo']);
+$as->logout(SimpleSAML_Utilities::checkURLAllowed($_REQUEST['ReturnTo']));
diff --git a/modules/core/www/bwc_resumeauth.php b/modules/core/www/bwc_resumeauth.php
index 68b205504..df937077d 100644
--- a/modules/core/www/bwc_resumeauth.php
+++ b/modules/core/www/bwc_resumeauth.php
@@ -20,7 +20,7 @@ if ($requestcache['ForceAuthn'] && $requestcache['core:prevSession'] === $sessio
 }
 
 if (isset($state['ReturnTo'])) {
-	SimpleSAML_Utilities::redirectUntrustedURL($state['ReturnTo']);
+	SimpleSAML_Utilities::redirectTrustedURL($state['ReturnTo']);
 }
 
 foreach ($session->getAuthState($authority) as $k => $v) {
diff --git a/modules/core/www/cleardiscochoices.php b/modules/core/www/cleardiscochoices.php
index c94e411e2..afd72997f 100644
--- a/modules/core/www/cleardiscochoices.php
+++ b/modules/core/www/cleardiscochoices.php
@@ -26,12 +26,12 @@ foreach($_COOKIE as $cookieName => $value) {
 
 /* Find where we should go now. */
 if(array_key_exists('ReturnTo', $_REQUEST)) {
-	$returnTo = $_REQUEST['ReturnTo'];
+	$returnTo = SimpleSAML_Utilities::checkURLAllowed($_REQUEST['ReturnTo']);
 } else {
 	/* Return to the front page if no other destination is given. This is the same as the base cookie path. */
 	$returnTo = $cookiePath;
 }
 
 /* Redirect to destination. */
-SimpleSAML_Utilities::redirectUntrustedURL($returnTo);
+SimpleSAML_Utilities::redirectTrustedURL($returnTo);
 
diff --git a/modules/core/www/idp/logout-iframe-done.php b/modules/core/www/idp/logout-iframe-done.php
index 72a1e26f7..62539988b 100644
--- a/modules/core/www/idp/logout-iframe-done.php
+++ b/modules/core/www/idp/logout-iframe-done.php
@@ -5,6 +5,12 @@ if (!isset($_REQUEST['id'])) {
 }
 $id = (string)$_REQUEST['id'];
 
+// sanitize the input
+$restartURL = SimpleSAML_Utilities::getURLFromStateID($id);
+if (!is_null($restartURL)) {
+	SimpleSAML_Utilities::checkURLAllowed($restartURL);
+}
+
 $state = SimpleSAML_Auth_State::loadState($id, 'core:Logout-IFrame');
 $idp = SimpleSAML_IdP::getByState($state);
 
diff --git a/modules/core/www/idp/logout-iframe.php b/modules/core/www/idp/logout-iframe.php
index 1b751e931..53cdcfc94 100644
--- a/modules/core/www/idp/logout-iframe.php
+++ b/modules/core/www/idp/logout-iframe.php
@@ -19,6 +19,12 @@ if ($type !== 'embed' && $type !== 'async') {
 	SimpleSAML_Stats::log('core:idp:logout-iframe:page', array('type' => $type));
 }
 
+// sanitize the input
+$restartURL = SimpleSAML_Utilities::getURLFromStateID($id);
+if (!is_null($restartURL)) {
+	SimpleSAML_Utilities::checkURLAllowed($restartURL);
+}
+
 $state = SimpleSAML_Auth_State::loadState($id, 'core:Logout-IFrame');
 $idp = SimpleSAML_IdP::getByState($state);
 
diff --git a/modules/core/www/idp/resumelogout.php b/modules/core/www/idp/resumelogout.php
index 37c3e60f5..f93c8e968 100644
--- a/modules/core/www/idp/resumelogout.php
+++ b/modules/core/www/idp/resumelogout.php
@@ -5,6 +5,12 @@ if (!isset($_REQUEST['id'])) {
 }
 $id = (string)$_REQUEST['id'];
 
+// sanitize the input
+$restartURL = SimpleSAML_Utilities::getURLFromStateID($id);
+if (!is_null($restartURL)) {
+	SimpleSAML_Utilities::checkURLAllowed($restartURL);
+}
+
 $state = SimpleSAML_Auth_State::loadState($id, 'core:Logout:afterbridge');
 $idp = SimpleSAML_IdP::getByState($state);
 
diff --git a/modules/core/www/login-admin.php b/modules/core/www/login-admin.php
index 83886a181..3e6438f3e 100644
--- a/modules/core/www/login-admin.php
+++ b/modules/core/www/login-admin.php
@@ -6,9 +6,8 @@
 if (!array_key_exists('ReturnTo', $_REQUEST)) {
 	throw new SimpleSAML_Error_BadRequest('Missing ReturnTo parameter.');
 }
-$returnTo = $_REQUEST['ReturnTo'];
 
 SimpleSAML_Utilities::requireAdmin();
 
-SimpleSAML_Utilities::redirectUntrustedURL($returnTo);
+SimpleSAML_Utilities::redirectUntrustedURL($_REQUEST['ReturnTo']);
 
diff --git a/modules/core/www/loginuserpass.php b/modules/core/www/loginuserpass.php
index 71da3aeae..cda363b40 100644
--- a/modules/core/www/loginuserpass.php
+++ b/modules/core/www/loginuserpass.php
@@ -15,6 +15,12 @@ if (!array_key_exists('AuthState', $_REQUEST)) {
 }
 $authStateId = $_REQUEST['AuthState'];
 
+// sanitize the input
+$restartURL = SimpleSAML_Utilities::getURLFromStateID($authStateId);
+if (!is_null($restartURL)) {
+	SimpleSAML_Utilities::checkURLAllowed($restartURL);
+}
+
 /* Retrieve the authentication state. */
 $state = SimpleSAML_Auth_State::loadState($authStateId, sspmod_core_Auth_UserPassBase::STAGEID);
 
diff --git a/modules/core/www/loginuserpassorg.php b/modules/core/www/loginuserpassorg.php
index cda773bfc..abd9a532e 100644
--- a/modules/core/www/loginuserpassorg.php
+++ b/modules/core/www/loginuserpassorg.php
@@ -15,6 +15,12 @@ if (!array_key_exists('AuthState', $_REQUEST)) {
 }
 $authStateId = $_REQUEST['AuthState'];
 
+// sanitize the input
+$restartURL = SimpleSAML_Utilities::getURLFromStateID($authStateId);
+if (!is_null($restartURL)) {
+	SimpleSAML_Utilities::checkURLAllowed($restartURL);
+}
+
 /* Retrieve the authentication state. */
 $state = SimpleSAML_Auth_State::loadState($authStateId, sspmod_core_Auth_UserPassOrgBase::STAGEID);
 
diff --git a/modules/core/www/short_sso_interval.php b/modules/core/www/short_sso_interval.php
index 5a51470a3..e9e5b159d 100644
--- a/modules/core/www/short_sso_interval.php
+++ b/modules/core/www/short_sso_interval.php
@@ -12,6 +12,13 @@ if (!array_key_exists('StateId', $_REQUEST)) {
 }
 
 $id = $_REQUEST['StateId'];
+
+// sanitize the input
+$restartURL = SimpleSAML_Utilities::getURLFromStateID($id);
+if (!is_null($restartURL)) {
+	SimpleSAML_Utilities::checkURLAllowed($restartURL);
+}
+
 $state = SimpleSAML_Auth_State::loadState($id, 'core:short_sso_interval');
 
 if (array_key_exists('continue', $_REQUEST)) {
diff --git a/modules/discopower/lib/PowerIdPDisco.php b/modules/discopower/lib/PowerIdPDisco.php
index 22459312b..10aad002f 100644
--- a/modules/discopower/lib/PowerIdPDisco.php
+++ b/modules/discopower/lib/PowerIdPDisco.php
@@ -203,7 +203,7 @@ class sspmod_discopower_PowerIdPDisco extends SimpleSAML_XHTML_IdPDisco {
 				
 			} else {
 				$this->log('Choice made [' . $idp . '] (Redirecting the user back. returnIDParam=' . $this->returnIdParam . ')');
-				SimpleSAML_Utilities::redirectUntrustedURL($this->returnURL, array($this->returnIdParam => $idp));
+				SimpleSAML_Utilities::redirectTrustedURL($this->returnURL, array($this->returnIdParam => $idp));
 			}
 			
 			return;
@@ -211,7 +211,7 @@ class sspmod_discopower_PowerIdPDisco extends SimpleSAML_XHTML_IdPDisco {
 		
 		if ($this->isPassive) {
 			$this->log('Choice not made. (Redirecting the user back without answer)');
-			SimpleSAML_Utilities::redirectUntrustedURL($this->returnURL);
+			SimpleSAML_Utilities::redirectTrustedURL($this->returnURL);
 			return;
 		}
 
diff --git a/modules/exampleauth/lib/Auth/Source/External.php b/modules/exampleauth/lib/Auth/Source/External.php
index d3b16f020..282c6a46d 100644
--- a/modules/exampleauth/lib/Auth/Source/External.php
+++ b/modules/exampleauth/lib/Auth/Source/External.php
@@ -186,6 +186,12 @@ class sspmod_exampleauth_Auth_Source_External extends SimpleSAML_Auth_Source {
 		}
 		$stateId = (string)$_REQUEST['State'];
 
+		// sanitize the input
+		$restartURL = SimpleSAML_Utilities::getURLFromStateID($stateId);
+		if (!is_null($restartURL)) {
+			SimpleSAML_Utilities::checkURLAllowed($restartURL);
+		}
+
 		/*
 		 * Once again, note the second parameter to the loadState function. This must
 		 * match the string we used in the saveState-call above.
diff --git a/modules/exampleauth/www/authpage.php b/modules/exampleauth/www/authpage.php
index 21a284b22..bcd01b8d4 100644
--- a/modules/exampleauth/www/authpage.php
+++ b/modules/exampleauth/www/authpage.php
@@ -14,7 +14,7 @@ if (!isset($_REQUEST['ReturnTo'])) {
 	die('Missing ReturnTo parameter.');
 }
 
-$returnTo = $_REQUEST['ReturnTo'];
+$returnTo = SimpleSAML_Utilities::checkURLAllowed($_REQUEST['ReturnTo']);
 
 
 /*
@@ -31,6 +31,13 @@ if (!preg_match('@State=(.*)@', $returnTo, $matches)) {
 	die('Invalid ReturnTo URL for this example.');
 }
 $stateId = urldecode($matches[1]);
+
+// sanitize the input
+$restartURL = SimpleSAML_Utilities::getURLFromStateID($stateId);
+if (!is_null($restartURL)) {
+	SimpleSAML_Utilities::checkURLAllowed($restartURL);
+}
+
 SimpleSAML_Auth_State::loadState($stateId, 'exampleauth:External');
 
 /*
@@ -87,8 +94,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
 		$_SESSION['mail'] = $user['mail'];
 		$_SESSION['type'] = $user['type'];
 
-		header('Location: ' . $returnTo);
-		exit();
+		SimpleSAML_Utilities::redirectTrustedURL($returnTo);
 	}
 }
 
diff --git a/modules/exampleauth/www/redirecttest.php b/modules/exampleauth/www/redirecttest.php
index 39a1b5450..c6d4fb86e 100644
--- a/modules/exampleauth/www/redirecttest.php
+++ b/modules/exampleauth/www/redirecttest.php
@@ -13,6 +13,13 @@ if (!array_key_exists('StateId', $_REQUEST)) {
 }
 
 $id = $_REQUEST['StateId'];
+
+// sanitize the input
+$restartURL = SimpleSAML_Utilities::getURLFromStateID($id);
+if (!is_null($restartURL)) {
+	SimpleSAML_Utilities::checkURLAllowed($restartURL);
+}
+
 $state = SimpleSAML_Auth_State::loadState($id, 'exampleauth:redirectfilter-test');
 
 $state['Attributes']['RedirectTest2'] = array('OK');
diff --git a/modules/expirycheck/www/about2expire.php b/modules/expirycheck/www/about2expire.php
index c69624723..487b3f8df 100644
--- a/modules/expirycheck/www/about2expire.php
+++ b/modules/expirycheck/www/about2expire.php
@@ -14,6 +14,13 @@ if (!array_key_exists('StateId', $_REQUEST)) {
 }
 
 $id = $_REQUEST['StateId'];
+
+// sanitize the input
+$restartURL = SimpleSAML_Utilities::getURLFromStateID($id);
+if (!is_null($restartURL)) {
+	SimpleSAML_Utilities::checkURLAllowed($restartURL);
+}
+
 $state = SimpleSAML_Auth_State::loadState($id, 'expirywarning:about2expire');
 
 
diff --git a/modules/expirycheck/www/expired.php b/modules/expirycheck/www/expired.php
index 9b94cadbc..5ec7b93a1 100644
--- a/modules/expirycheck/www/expired.php
+++ b/modules/expirycheck/www/expired.php
@@ -14,6 +14,13 @@ if (!array_key_exists('StateId', $_REQUEST)) {
 }
 
 $id = $_REQUEST['StateId'];
+
+// sanitize the input
+$restartURL = SimpleSAML_Utilities::getURLFromStateID($id);
+if (!is_null($restartURL)) {
+	SimpleSAML_Utilities::checkURLAllowed($restartURL);
+}
+
 $state = SimpleSAML_Auth_State::loadState($id, 'expirywarning:expired');
 
 $globalConfig = SimpleSAML_Configuration::getInstance();
diff --git a/modules/multiauth/www/selectsource.php b/modules/multiauth/www/selectsource.php
index 329a1bf49..afa28ed50 100644
--- a/modules/multiauth/www/selectsource.php
+++ b/modules/multiauth/www/selectsource.php
@@ -16,6 +16,12 @@ if (!array_key_exists('AuthState', $_REQUEST)) {
 }
 $authStateId = $_REQUEST['AuthState'];
 
+// sanitize the input
+$restartURL = SimpleSAML_Utilities::getURLFromStateID($authStateId);
+if (!is_null($restartURL)) {
+	SimpleSAML_Utilities::checkURLAllowed($restartURL);
+}
+
 /* Retrieve the authentication state. */
 $state = SimpleSAML_Auth_State::loadState($authStateId, sspmod_multiauth_Auth_Source_MultiAuth::STAGEID);
 
diff --git a/modules/negotiate/www/backend.php b/modules/negotiate/www/backend.php
index f9d362229..347ce8dc9 100644
--- a/modules/negotiate/www/backend.php
+++ b/modules/negotiate/www/backend.php
@@ -10,6 +10,13 @@
  */
 
 $authStateId = $_REQUEST['AuthState'];
+
+// sanitize the input
+$restartURL = SimpleSAML_Utilities::getURLFromStateID($authStateId);
+if (!is_null($restartURL)) {
+	SimpleSAML_Utilities::checkURLAllowed($restartURL);
+}
+
 $state = SimpleSAML_Auth_State::loadState($authStateId, sspmod_negotiate_Auth_Source_Negotiate::STAGEID);
 SimpleSAML_Logger::debug('backend - fallback: '.$state['LogoutState']['negotiate:backend']);
 
diff --git a/modules/negotiate/www/retry.php b/modules/negotiate/www/retry.php
index 2d12eab97..858b83678 100644
--- a/modules/negotiate/www/retry.php
+++ b/modules/negotiate/www/retry.php
@@ -10,6 +10,13 @@
  */
 
 $authStateId = $_REQUEST['AuthState'];
+
+// sanitize the input
+$restartURL = SimpleSAML_Utilities::getURLFromStateID($authStateId);
+if (!is_null($restartURL)) {
+	SimpleSAML_Utilities::checkURLAllowed($restartURL);
+}
+
 $state = SimpleSAML_Auth_State::loadState($authStateId, sspmod_negotiate_Auth_Source_Negotiate::STAGEID);
 
 $metadata = SimpleSAML_Metadata_MetaDataStorageHandler::getMetadataHandler();
diff --git a/modules/oauth/www/authorize.php b/modules/oauth/www/authorize.php
index 9b3e032e1..583c86efc 100644
--- a/modules/oauth/www/authorize.php
+++ b/modules/oauth/www/authorize.php
@@ -56,7 +56,7 @@ try {
 
 	if ($url) {
 		// If authorize() returns a URL, take user there (oauth1.0a)
-		SimpleSAML_Utilities::redirectUntrustedURL($url);
+		SimpleSAML_Utilities::redirectTrustedURL($url);
 	} 
 	else if (isset($_REQUEST['oauth_callback'])) {
 		// If callback was provided in the request (oauth1.0)
diff --git a/modules/openid/www/consumer.php b/modules/openid/www/consumer.php
index 889fbf59c..0f8067c0e 100644
--- a/modules/openid/www/consumer.php
+++ b/modules/openid/www/consumer.php
@@ -6,6 +6,13 @@ if (!array_key_exists('AuthState', $_REQUEST) || empty($_REQUEST['AuthState']))
 }
 
 $authState = $_REQUEST['AuthState'];
+
+// sanitize the input
+$restartURL = SimpleSAML_Utilities::getURLFromStateID($authState);
+if (!is_null($restartURL)) {
+	SimpleSAML_Utilities::checkURLAllowed($restartURL);
+}
+
 $state = SimpleSAML_Auth_State::loadState($authState, 'openid:init');
 $sourceId = $state['openid:AuthId'];
 $authSource = SimpleSAML_Auth_Source::getById($sourceId);
diff --git a/modules/openid/www/linkback.php b/modules/openid/www/linkback.php
index 180f9e18e..6108ca73e 100644
--- a/modules/openid/www/linkback.php
+++ b/modules/openid/www/linkback.php
@@ -6,6 +6,13 @@ if (!array_key_exists('AuthState', $_REQUEST) || empty($_REQUEST['AuthState']))
 }
 
 $authState = $_REQUEST['AuthState'];
+
+// sanitize the input
+$restartURL = SimpleSAML_Utilities::getURLFromStateID($authState);
+if (!is_null($restartURL)) {
+	SimpleSAML_Utilities::checkURLAllowed($restartURL);
+}
+
 $state = SimpleSAML_Auth_State::loadState($authState, 'openid:auth');
 $sourceId = $state['openid:AuthId'];
 $authSource = SimpleSAML_Auth_Source::getById($sourceId);
diff --git a/modules/openidProvider/lib/Server.php b/modules/openidProvider/lib/Server.php
index f0596d18f..1382cd84d 100644
--- a/modules/openidProvider/lib/Server.php
+++ b/modules/openidProvider/lib/Server.php
@@ -329,6 +329,12 @@ class sspmod_openidProvider_Server {
 	public function loadState($stateId) {
 		assert('is_string($stateId)');
 
+		// sanitize the input
+		$restartURL = SimpleSAML_Utilities::getURLFromStateID($stateId);
+		if (!is_null($restartURL)) {
+			SimpleSAML_Utilities::checkURLAllowed($restartURL);
+		}
+
 		return SimpleSAML_Auth_State::loadState($stateId, 'openidProvider:resumeState');
 	}
 
diff --git a/modules/papi/lib/Auth/Source/PAPI.php b/modules/papi/lib/Auth/Source/PAPI.php
index ead66c883..c07409346 100644
--- a/modules/papi/lib/Auth/Source/PAPI.php
+++ b/modules/papi/lib/Auth/Source/PAPI.php
@@ -115,6 +115,13 @@ class sspmod_papi_Auth_Source_PAPI extends SimpleSAML_Auth_Source {
         if (isset($_REQUEST['SSPStateID'])) {
 			// yes! restore original request
            	$this->_stateId = (string)$_REQUEST['SSPStateID'];
+           	
+			// sanitize the input
+			$restartURL = SimpleSAML_Utilities::getURLFromStateID($this->_stateId);
+			if (!is_null($restartURL)) {
+				SimpleSAML_Utilities::checkURLAllowed($restartURL);
+			}
+
            	$state = SimpleSAML_Auth_State::loadState($this->_stateId, self::STAGE_INIT);
 		} else if (!$this->_poa->isAuthenticated()) { 
 			// no! we have to save the request
@@ -161,6 +168,13 @@ class sspmod_papi_Auth_Source_PAPI extends SimpleSAML_Auth_Source {
     		$this->_poa->logout(true);
     	} else if (isset($_REQUEST['SSPStateID'])) {
     		$this->_stateId = (string)$_REQUEST['SSPStateID'];
+
+			// sanitize the input
+			$restartURL = SimpleSAML_Utilities::getURLFromStateID($this->_stateId);
+			if (!is_null($restartURL)) {
+				SimpleSAML_Utilities::checkURLAllowed($restartURL);
+			}
+
     		$state = SimpleSAML_Auth_State::loadState($this->_stateId, self::STAGE_INIT);
     	} else {
     		return;
diff --git a/modules/preprodwarning/www/showwarning.php b/modules/preprodwarning/www/showwarning.php
index d8db64560..2c50860dd 100644
--- a/modules/preprodwarning/www/showwarning.php
+++ b/modules/preprodwarning/www/showwarning.php
@@ -15,6 +15,13 @@ if (!array_key_exists('StateId', $_REQUEST)) {
 }
 
 $id = $_REQUEST['StateId'];
+
+// sanitize the input
+$restartURL = SimpleSAML_Utilities::getURLFromStateID($id);
+if (!is_null($restartURL)) {
+	SimpleSAML_Utilities::checkURLAllowed($restartURL);
+}
+
 $state = SimpleSAML_Auth_State::loadState($id, 'warning:request');
 
 
diff --git a/modules/saml/www/sp/discoresp.php b/modules/saml/www/sp/discoresp.php
index 6d9af381e..1479f8a42 100644
--- a/modules/saml/www/sp/discoresp.php
+++ b/modules/saml/www/sp/discoresp.php
@@ -12,7 +12,15 @@ if (!array_key_exists('idpentityid', $_REQUEST)) {
 	throw new SimpleSAML_Error_BadRequest('Missing idpentityid to discovery service response handler');
 }
 
-$state = SimpleSAML_Auth_State::loadState($_REQUEST['AuthID'], 'saml:sp:sso');
+$stateID = $_REQUEST['AuthID'];
+
+// sanitize the input
+$restartURL = SimpleSAML_Utilities::getURLFromStateID($stateID);
+if (!is_null($restartURL)) {
+	SimpleSAML_Utilities::checkURLAllowed($restartURL);
+}
+
+$state = SimpleSAML_Auth_State::loadState($stateID, 'saml:sp:sso');
 
 /* Find authentication source. */
 assert('array_key_exists("saml:sp:AuthId", $state)');
diff --git a/modules/saml/www/sp/saml1-acs.php b/modules/saml/www/sp/saml1-acs.php
index 28ba71143..d9a594c5e 100644
--- a/modules/saml/www/sp/saml1-acs.php
+++ b/modules/saml/www/sp/saml1-acs.php
@@ -19,17 +19,25 @@ $source = SimpleSAML_Auth_Source::getById($sourceId, 'sspmod_saml_Auth_Source_SP
 
 SimpleSAML_Logger::debug('Received SAML1 response');
 
-
 $target = (string)$_REQUEST['TARGET'];
+
 if (preg_match('@^https?://@i', $target)) {
 	/* Unsolicited response. */
 	$state = array(
 		'saml:sp:isUnsolicited' => TRUE,
 		'saml:sp:AuthId' => $sourceId,
-		'saml:sp:RelayState' => $target,
+		'saml:sp:RelayState' => SimpleSAML_Utilities::checkURLAllowed($target),
 	);
 } else {
-	$state = SimpleSAML_Auth_State::loadState($_REQUEST['TARGET'], 'saml:sp:sso');
+	$stateID = $_REQUEST['TARGET'];
+
+	// sanitize the input
+	$restartURL = SimpleSAML_Utilities::getURLFromStateID($stateID);
+	if (!is_null($restartURL)) {
+		SimpleSAML_Utilities::checkURLAllowed($restartURL);
+	}
+
+	$state = SimpleSAML_Auth_State::loadState($stateID, 'saml:sp:sso');
 
 	/* Check that the authentication source is correct. */
 	assert('array_key_exists("saml:sp:AuthId", $state)');
@@ -80,4 +88,4 @@ $state['LogoutState'] = $logoutState;
 $source->handleResponse($state, $responseIssuer, $attributes);
 assert('FALSE');
 
-?>
\ No newline at end of file
+?>
diff --git a/modules/saml/www/sp/saml2-acs.php b/modules/saml/www/sp/saml2-acs.php
index a3c8200bc..09723b642 100644
--- a/modules/saml/www/sp/saml2-acs.php
+++ b/modules/saml/www/sp/saml2-acs.php
@@ -52,6 +52,13 @@ $idpMetadata = array();
 
 $stateId = $response->getInResponseTo();
 if (!empty($stateId)) {
+
+	// sanitize the input
+	$restartURL = SimpleSAML_Utilities::getURLFromStateID($stateId);
+	if (!is_null($restartURL)) {
+		SimpleSAML_Utilities::checkURLAllowed($restartURL);
+	}
+
 	/* This is a response to a request we sent earlier. */
 	$state = SimpleSAML_Auth_State::loadState($stateId, 'saml:sp:sso');
 
@@ -75,7 +82,7 @@ if (!empty($stateId)) {
 	$state = array(
 		'saml:sp:isUnsolicited' => TRUE,
 		'saml:sp:AuthId' => $sourceId,
-		'saml:sp:RelayState' => $response->getRelayState(),
+		'saml:sp:RelayState' => SimpleSAML_Utilities::checkURLAllowed($response->getRelayState()),
 	);
 }
 
diff --git a/modules/saml/www/sp/saml2-logout.php b/modules/saml/www/sp/saml2-logout.php
index d2b26b06e..5d3602433 100644
--- a/modules/saml/www/sp/saml2-logout.php
+++ b/modules/saml/www/sp/saml2-logout.php
@@ -54,6 +54,12 @@ if ($message instanceof SAML2_LogoutResponse) {
 		SimpleSAML_Logger::warning('Unsuccessful logout. Status was: ' . sspmod_saml_Message::getResponseError($message));
 	}
 
+	// sanitize the input
+	$restartURL = SimpleSAML_Utilities::getURLFromStateID($relayState);
+	if (!is_null($restartURL)) {
+		SimpleSAML_Utilities::checkURLAllowed($restartURL);
+	}
+
 	$state = SimpleSAML_Auth_State::loadState($relayState, 'saml:slosent');
 	$state['saml:sp:LogoutStatus'] = $message->getStatus();
 	SimpleSAML_Auth_Source::completeLogout($state);
diff --git a/www/auth/login-admin.php b/www/auth/login-admin.php
index 42ac0ecf0..597b353aa 100644
--- a/www/auth/login-admin.php
+++ b/www/auth/login-admin.php
@@ -20,7 +20,7 @@ if (!array_key_exists('RelayState', $_REQUEST)) {
 	throw new SimpleSAML_Error_Error('NORELAYSTATE');
 }
 
-$relaystate = $_REQUEST['RelayState'];
+$relaystate = SimpleSAML_Utilities::checkURLAllowed($_REQUEST['RelayState']);
 
 $correctpassword = $config->getString('auth.adminpassword', '123');
 
@@ -59,7 +59,7 @@ if (isset($_POST['password'])) {
 		else 
 			SimpleSAML_Logger::stats('AUTH-login-admin OK');
 		
-		SimpleSAML_Utilities::redirectUntrustedURL($relaystate);
+		SimpleSAML_Utilities::redirectTrustedURL($relaystate);
 		exit(0);
 	} else {
 		SimpleSAML_Logger::stats('AUTH-login-admin Failed');
@@ -82,6 +82,3 @@ if (isset($error)) {
 }
 
 $t->show();
-
-
-?>
diff --git a/www/auth/login-cas-ldap.php b/www/auth/login-cas-ldap.php
index 2ba907ade..281488d97 100644
--- a/www/auth/login-cas-ldap.php
+++ b/www/auth/login-cas-ldap.php
@@ -30,8 +30,6 @@ try {
 
 	$casconfig = $casldapconfig[$idpentityid]['cas'];
 	$ldapconfig = $casldapconfig[$idpentityid]['ldap'];
-	
-	
 } catch (Exception $exception) {
 	throw new SimpleSAML_Error_Error('METADATA', $exception);
 }
@@ -44,8 +42,6 @@ if (!array_key_exists('RelayState', $_REQUEST)) {
 	throw new SimpleSAML_Error_Error('NORELAYSTATE');
 }
 
-
-
 function casValidate($cas) {
 
 	$service = SimpleSAML_Utilities::selfURL();
@@ -110,12 +106,7 @@ function casValidate($cas) {
 	}
 }
 
-
-
 try {
-	
-	$relaystate = $_REQUEST['RelayState'];
-
 	list($username, $casattributes) = casValidate($casconfig);
 	
 	SimpleSAML_Logger::info('AUTH - cas-ldap: '. $username . ' authenticated by ' . $casconfig['validate']);
@@ -132,11 +123,9 @@ try {
 	$session->setNameID(array(
 			'value' => SimpleSAML_Utilities::generateID(),
 			'Format' => 'urn:oasis:names:tc:SAML:2.0:nameid-format:transient'));
-	SimpleSAML_Utilities::redirectUntrustedURL($relaystate);
+
+	SimpleSAML_Utilities::redirectUntrustedURL($_REQUEST['RelayState']);
 
 } catch(Exception $exception) {
 	throw new SimpleSAML_Error_Error('CASERROR', $exception);
 }
-
-
-?>
\ No newline at end of file
diff --git a/www/auth/login-ldapmulti.php b/www/auth/login-ldapmulti.php
index aab198afc..169800bc3 100644
--- a/www/auth/login-ldapmulti.php
+++ b/www/auth/login-ldapmulti.php
@@ -10,7 +10,6 @@ $session = SimpleSAML_Session::getInstance();
 $ldapconfigfile = $config->getBaseDir() . 'config/ldapmulti.php';
 require_once($ldapconfigfile);
 
-
 SimpleSAML_Logger::info('AUTH - ldap-multi: Accessing auth endpoint login-ldapmulti');
 
 $error = null;
@@ -23,6 +22,8 @@ if (!array_key_exists('RelayState', $_REQUEST)) {
 	throw new SimpleSAML_Error_Error('NORELAYSTATE');
 }
 
+$relaystate = SimpleSAML_Utilities::checkURLAllowed($_REQUEST['RelayState']);
+
 if (isset($_POST['username'])) {
 
 	try {
@@ -50,8 +51,7 @@ if (isset($_POST['username'])) {
 		$attributes = $ldap->getAttributes($dn, $ldapconfig['attributes']);
 						
 		SimpleSAML_Logger::info('AUTH - ldap-multi: '. $_POST['username'] . ' successfully authenticated');
-				
-				
+
 		$session->doLogin('login-ldapmulti');
 		$session->setAttributes($attributes);
 				
@@ -64,19 +64,16 @@ if (isset($_POST['username'])) {
 		 * Also log a specific attribute as set in the config: statistics.authlogattr
 		 */
 		$authlogattr = $config->getValue('statistics.authlogattr', null);
-		if ($authlogattr && array_key_exists($authlogattr, $attributes)) 
+		if ($authlogattr && array_key_exists($authlogattr, $attributes)) {
 			SimpleSAML_Logger::stats('AUTH-login-ldapmulti OK ' . $attributes[$authlogattr][0]);
-		else 
+		} else {
 			SimpleSAML_Logger::stats('AUTH-login-ldapmulti OK');
-				
-				
-		$returnto = $_REQUEST['RelayState'];
-		SimpleSAML_Utilities::redirectUntrustedURL($returnto);
+		}
+
+		SimpleSAML_Utilities::redirectTrustedURL($relaystate);
 
 	} catch (Exception $e) {
-		
 		$error = $e->getMessage();
-		
 	}	
 }
 
@@ -84,7 +81,7 @@ if (isset($_POST['username'])) {
 $t = new SimpleSAML_XHTML_Template($config, 'login-ldapmulti.php', 'login');
 
 $t->data['header'] = 'simpleSAMLphp: Enter username and password';	
-$t->data['relaystate'] = $_REQUEST['RelayState'];
+$t->data['relaystate'] = $relaystate;
 $t->data['ldapconfig'] = $ldapmulti;
 $t->data['org'] = $_REQUEST['org'];
 $t->data['error'] = $error;
diff --git a/www/auth/login-radius.php b/www/auth/login-radius.php
index b95c7ae92..5a80f49f0 100644
--- a/www/auth/login-radius.php
+++ b/www/auth/login-radius.php
@@ -19,6 +19,8 @@ if (!array_key_exists('RelayState', $_REQUEST)) {
 	throw new SimpleSAML_Error_Error('NORELAYSTATE');
 }
 
+$relaystate = SimpleSAML_Utilities::checkURLAllowed($_REQUEST['RelayState']);
+
 if (isset($_POST['username'])) {
 
 
@@ -97,21 +99,18 @@ if (isset($_POST['username'])) {
 					'value' => SimpleSAML_Utilities::generateID(),
 					'Format' => 'urn:oasis:names:tc:SAML:2.0:nameid-format:transient'));
 
-				
 				/**
 				 * Create a statistics log entry for every successfull login attempt.
 				 * Also log a specific attribute as set in the config: statistics.authlogattr
 				 */
 				$authlogattr = $config->getValue('statistics.authlogattr', null);
-				if ($authlogattr && array_key_exists($authlogattr, $attributes)) 
+				if ($authlogattr && array_key_exists($authlogattr, $attributes)) {
 					SimpleSAML_Logger::stats('AUTH-login-radius OK ' . $attributes[$authlogattr][0]);
-				else 
+				} else {
 					SimpleSAML_Logger::stats('AUTH-login-radius OK');
+				}
 
-	
-				$returnto = $_REQUEST['RelayState'];
-				SimpleSAML_Utilities::redirectUntrustedURL($returnto);
-				
+				SimpleSAML_Utilities::redirectTrustedURL($relaystate);
 	
 			case RADIUS_ACCESS_REJECT:
 			
@@ -125,13 +124,10 @@ if (isset($_POST['username'])) {
 			default:
 				SimpleSAML_Logger::critical('AUTH  -radius: General radius error: ' . radius_strerror($radius));
 				throw new Exception('Error during radius authentication: ' . radius_strerror($radius));
-				
 		}
 
 	} catch (Exception $e) {
-		
 		$error = $e->getMessage();
-		
 	}
 }
 
@@ -139,13 +135,10 @@ if (isset($_POST['username'])) {
 $t = new SimpleSAML_XHTML_Template($config, 'login.php', 'login');
 
 $t->data['header'] = 'simpleSAMLphp: Enter username and password';	
-$t->data['relaystate'] = $_REQUEST['RelayState'];
+$t->data['relaystate'] = $relaystate;
 $t->data['error'] = $error;
 if (isset($error)) {
 	$t->data['username'] = $_POST['username'];
 }
 
 $t->show();
-
-
-?>
diff --git a/www/auth/login-tlsclient.php b/www/auth/login-tlsclient.php
index 42e2678ae..0fb6e9392 100644
--- a/www/auth/login-tlsclient.php
+++ b/www/auth/login-tlsclient.php
@@ -23,9 +23,6 @@ if (!array_key_exists('RelayState', $_REQUEST)) {
 	throw new SimpleSAML_Error_Error('NORELAYSTATE');
 }
 
-$relaystate = $_REQUEST['RelayState'];
-
-
 try {
 
 	$attributes = array();
@@ -55,21 +52,21 @@ try {
 	
 	$session->setNameID(array(
 		'value' => SimpleSAML_Utilities::generateID(),
-		'Format' => 'urn:oasis:names:tc:SAML:2.0:nameid-format:transient'));
+		'Format' => 'urn:oasis:names:tc:SAML:2.0:nameid-format:transient')
+	);
 		
 	/**
 	 * Create a statistics log entry for every successfull login attempt.
 	 * Also log a specific attribute as set in the config: statistics.authlogattr
 	 */
 	$authlogattr = $config->getValue('statistics.authlogattr', null);
-	if ($authlogattr && array_key_exists($authlogattr, $attributes)) 
+	if ($authlogattr && array_key_exists($authlogattr, $attributes)) {
 		SimpleSAML_Logger::stats('AUTH-tlsclient OK ' . $attributes[$authlogattr][0]);
-	else 
+	} else {
 		SimpleSAML_Logger::stats('AUTH-tlsclient OK');
-		
+	}
 
-	$returnto = $_REQUEST['RelayState'];
-	SimpleSAML_Utilities::redirectUntrustedURL($returnto);
+	SimpleSAML_Utilities::redirectUntrustedURL($_REQUEST['RelayState']);
 	
 	
 } catch (Exception $e) {
diff --git a/www/auth/login-wayf-ldap.php b/www/auth/login-wayf-ldap.php
index 73d5f57a4..9c8c25e52 100644
--- a/www/auth/login-wayf-ldap.php
+++ b/www/auth/login-wayf-ldap.php
@@ -42,7 +42,7 @@ if (!array_key_exists('RelayState', $_REQUEST)) {
 	throw new SimpleSAML_Error_Error('NORELAYSTATE');
 }
 
-$relaystate = $_REQUEST['RelayState'];
+$relaystate = SimpleSAML_Utilities::checkURLAllowed($_REQUEST['RelayState']);
 
 if ($username = $_POST['username']) {
 	try {
@@ -59,7 +59,7 @@ if ($username = $_POST['username']) {
 			$session->setNameID(array(
 					'value' => SimpleSAML_Utilities::generateID(),
 					'Format' => 'urn:oasis:names:tc:SAML:2.0:nameid-format:transient'));
-			SimpleSAML_Utilities::redirectUntrustedURL($relaystate);
+			SimpleSAML_Utilities::redirectTrustedURL($relaystate);
 		}
 	} catch(Exception $e) {
 			throw new SimpleSAML_Error_Error('LDAPERROR', $e);
diff --git a/www/saml2/idp/SingleLogoutService.php b/www/saml2/idp/SingleLogoutService.php
index 618068cd8..45a1012a4 100644
--- a/www/saml2/idp/SingleLogoutService.php
+++ b/www/saml2/idp/SingleLogoutService.php
@@ -18,7 +18,7 @@ $idpEntityId = $metadata->getMetaDataCurrentEntityID('saml20-idp-hosted');
 $idp = SimpleSAML_IdP::getById('saml2:' . $idpEntityId);
 
 if (isset($_REQUEST['ReturnTo'])) {
-	$idp->doLogoutRedirect((string)$_REQUEST['ReturnTo']);
+	$idp->doLogoutRedirect(SimpleSAML_Utilities::checkURLAllowed((string)$_REQUEST['ReturnTo']));
 } else {
 	sspmod_saml_IdP_SAML2::receiveLogoutMessage($idp);
 }
diff --git a/www/saml2/idp/idpInitSingleLogoutServiceiFrame.php b/www/saml2/idp/idpInitSingleLogoutServiceiFrame.php
index 24f89b013..977265932 100644
--- a/www/saml2/idp/idpInitSingleLogoutServiceiFrame.php
+++ b/www/saml2/idp/idpInitSingleLogoutServiceiFrame.php
@@ -16,5 +16,5 @@ if (!isset($_REQUEST['RelayState'])) {
 	throw new SimpleSAML_Error_BadRequest('Missing required RelayState parameter.');
 }
 
-$idp->doLogoutRedirect((string)$_REQUEST['RelayState']);
+$idp->doLogoutRedirect(SimpleSAML_Utilities::checkURLAllowed((string)$_REQUEST['RelayState']));
 assert('FALSE');
diff --git a/www/saml2/idp/initSLO.php b/www/saml2/idp/initSLO.php
index d6b23adc3..87191b777 100644
--- a/www/saml2/idp/initSLO.php
+++ b/www/saml2/idp/initSLO.php
@@ -11,6 +11,5 @@ if (!isset($_GET['RelayState'])) {
 	throw new SimpleSAML_Error_Error('NORELAYSTATE');
 }
 
-$returnTo = (string)$_GET['RelayState'];
-$idp->doLogoutRedirect($returnTo);
+$idp->doLogoutRedirect(SimpleSAML_Utilities::checkURLAllowed((string)$_GET['RelayState']));
 assert('FALSE');
\ No newline at end of file
diff --git a/www/saml2/sp/AssertionConsumerService.php b/www/saml2/sp/AssertionConsumerService.php
index e024489ba..c7dc96f29 100644
--- a/www/saml2/sp/AssertionConsumerService.php
+++ b/www/saml2/sp/AssertionConsumerService.php
@@ -47,7 +47,7 @@ function finishLogin($authProcState) {
 	global $session;
 	$session->doLogin('saml2', $authData);
 
-	SimpleSAML_Utilities::redirectUntrustedURL($authProcState['core:saml20-sp:TargetURL']);
+	SimpleSAML_Utilities::redirectTrustedURL($authProcState['core:saml20-sp:TargetURL']);
 }
 
 SimpleSAML_Logger::info('SAML2.0 - SP.AssertionConsumerService: Accessing SAML 2.0 SP endpoint AssertionConsumerService');
@@ -59,6 +59,13 @@ if (array_key_exists(SimpleSAML_Auth_ProcessingChain::AUTHPARAM, $_REQUEST)) {
 	/* We have returned from the authentication processing filters. */
 
 	$authProcId = $_REQUEST[SimpleSAML_Auth_ProcessingChain::AUTHPARAM];
+
+	// sanitize the input
+	$restartURL = SimpleSAML_Utilities::getURLFromStateID($authProcId);
+	if (!is_null($restartURL)) {
+		SimpleSAML_Utilities::checkURLAllowed($restartURL);
+	}
+
 	$authProcState = SimpleSAML_Auth_ProcessingChain::fetchProcessedState($authProcId);
 	finishLogin($authProcState);
 }
@@ -93,7 +100,7 @@ try {
 	if($info === NULL) {
 		/* Fall back to RelayState. */
 		$info = array();
-		$info['RelayState'] = $response->getRelayState();
+		$info['RelayState'] = SimpleSAML_Utilities::checkURLAllowed($response->getRelayState());
 		if(empty($info['RelayState'])) {
 			$info['RelayState'] = $spMetadata->getString('RelayState', NULL);
 		}
diff --git a/www/saml2/sp/SingleLogoutService.php b/www/saml2/sp/SingleLogoutService.php
index 0bd8c731e..cf9f94cd0 100644
--- a/www/saml2/sp/SingleLogoutService.php
+++ b/www/saml2/sp/SingleLogoutService.php
@@ -83,12 +83,14 @@ if ($message instanceof SAML2_LogoutRequest) {
 		$id = $message->getInResponseTo();
 	}
 
+	// 'spLogoutReturnTo' is checked before storing it in the
+	// session, so we trust it here.
 	$returnTo = $session->getData('spLogoutReturnTo', $id);
 	if (empty($returnTo)) {
 		throw new SimpleSAML_Error_Error('LOGOUTINFOLOST');
 	}
 
-	SimpleSAML_Utilities::redirectUntrustedURL($returnTo);
+	SimpleSAML_Utilities::redirectTrustedURL($returnTo);
 
 } else {
 	throw new SimpleSAML_Error_Error('SLOSERVICEPARAMS');
diff --git a/www/saml2/sp/initSLO.php b/www/saml2/sp/initSLO.php
index 8402a36c0..85c13bbe0 100644
--- a/www/saml2/sp/initSLO.php
+++ b/www/saml2/sp/initSLO.php
@@ -13,7 +13,7 @@ if (!$config->getBoolean('enable.saml20-sp', TRUE))
 
 
 if (isset($_REQUEST['RelayState'])) {
-	$returnTo = $_REQUEST['RelayState'];
+	$returnTo = SimpleSAML_Utilities::checkURLAllowed($_REQUEST['RelayState']);
 } else {
 	throw new SimpleSAML_Error_Error('NORELAYSTATE');
 }
@@ -25,7 +25,7 @@ try {
 	$idpEntityId = $session->getAuthData('saml2', 'saml:sp:IdP');
 	if ($idpEntityId === NULL) {
 		SimpleSAML_Logger::info('SAML2.0 - SP.initSLO: User not authenticated with an IdP.');
-		SimpleSAML_Utilities::redirectUntrustedURL($returnTo);
+		SimpleSAML_Utilities::redirectTrustedURL($returnTo);
 	}
 	$idpMetadata = $metadata->getMetaDataConfig($idpEntityId, 'saml20-idp-remote');
 	$SLOendpoint = $idpMetadata->getEndpointPrioritizedByBinding('SingleLogoutService', array(
@@ -34,8 +34,8 @@ try {
 		NULL);
 	if ($SLOendpoint === NULL) {
 		$session->doLogout('saml2');
-		SimpleSAML_Logger::info('SAML2.0 - SP.initSLO: No supported SingleLogoutService endpoint in IdP.');
-		SimpleSAML_Utilities::redirectUntrustedURL($returnTo);
+		SimpleSAML_Logger::info('SAML2.0 - SP.initSLO: No SingleLogoutService endpoint supported in the IdP.');
+		SimpleSAML_Utilities::redirectTrustedURL($returnTo);
 	}
 
 	$spEntityId = isset($_GET['spentityid']) ? $_GET['spentityid'] : $metadata->getMetaDataCurrentEntityID();
diff --git a/www/saml2/sp/initSSO.php b/www/saml2/sp/initSSO.php
index a9ee60fbe..c6d178036 100644
--- a/www/saml2/sp/initSSO.php
+++ b/www/saml2/sp/initSSO.php
@@ -23,6 +23,7 @@ if (!$config->getBoolean('enable.saml20-sp', TRUE))
 if (empty($_GET['RelayState'])) {
 	throw new SimpleSAML_Error_Error('NORELAYSTATE');
 }
+$returnTo = SimpleSAML_Utilities::checkURLAllowed($_GET['RelayState']);
 
 $reachableIDPs = array();
 
@@ -134,7 +135,7 @@ try {
 
 	$assertionConsumerServiceURL = $metadata->getGenerated('AssertionConsumerService', 'saml20-sp-hosted');
 	$ar->setAssertionConsumerServiceURL($assertionConsumerServiceURL);
-	$ar->setRelayState($_REQUEST['RelayState']);
+	$ar->setRelayState($returnTo);
 
 	if ($isPassive) {
 		$ar->setIsPassive(TRUE);
@@ -156,9 +157,9 @@ try {
 
 	/* Save request information. */
 	$info = array();
-	$info['RelayState'] = $_REQUEST['RelayState'];
+	$info['RelayState'] = $returnTo;
 	if(array_key_exists('OnError', $_REQUEST)) {
-		$info['OnError'] = $_REQUEST['OnError'];
+		$info['OnError'] = SimpleSAML_Utilities::checkURLAllowed($_REQUEST['OnError']);
 	}
 	$session->setData('SAML2:SP:SSO:Info', $ar->getId(), $info);
 
diff --git a/www/shib13/sp/AssertionConsumerService.php b/www/shib13/sp/AssertionConsumerService.php
index f523c28a4..bc5a4368c 100644
--- a/www/shib13/sp/AssertionConsumerService.php
+++ b/www/shib13/sp/AssertionConsumerService.php
@@ -47,6 +47,13 @@ if (array_key_exists(SimpleSAML_Auth_ProcessingChain::AUTHPARAM, $_REQUEST)) {
 	/* We have returned from the authentication processing filters. */
 
 	$authProcId = $_REQUEST[SimpleSAML_Auth_ProcessingChain::AUTHPARAM];
+
+	// sanitize the input
+	$restartURL = SimpleSAML_Utilities::getURLFromStateID($authProcId);
+	if (!is_null($restartURL)) {
+		SimpleSAML_Utilities::checkURLAllowed($restartURL);
+	}
+
 	$authProcState = SimpleSAML_Auth_ProcessingChain::fetchProcessedState($authProcId);
 	finishLogin($authProcState);
 }
@@ -86,7 +93,7 @@ try {
 	$authProcState = array(
 		'core:shib13-sp:NameID' => $authnResponse->getNameID(),
 		'core:shib13-sp:SessionIndex' => $authnResponse->getSessionIndex(),
-		'core:shib13-sp:TargetURL' => $relayState,
+		'core:shib13-sp:TargetURL' => SimpleSAML_Utilities::checkURLAllowed($relayState),
 		'ReturnURL' => SimpleSAML_Utilities::selfURLNoQuery(),
 		'Attributes' => $authnResponse->getAttributes(),
 		'Destination' => $spmetadata,
diff --git a/www/shib13/sp/initSSO.php b/www/shib13/sp/initSSO.php
index d666b8b86..5d40daae2 100644
--- a/www/shib13/sp/initSSO.php
+++ b/www/shib13/sp/initSSO.php
@@ -70,7 +70,7 @@ if (!isset($session) || !$session->isValid('shib13') ) {
 		$ar = new SimpleSAML_XML_Shib13_AuthnRequest();
 		$ar->setIssuer($spentityid);	
 		if(isset($_GET['RelayState'])) 
-			$ar->setRelayState($_GET['RelayState']);
+			$ar->setRelayState(SimpleSAML_Utilities::checkURLAllowed($_GET['RelayState']));
 
 		SimpleSAML_Logger::info('Shib1.3 - SP.initSSO: SP (' . $spentityid . ') is sending AuthNRequest to IdP (' . $idpentityid . ')');
 
diff --git a/www/wsfed/sp/initSLO.php b/www/wsfed/sp/initSLO.php
index f31ebaaca..318bfa2fe 100644
--- a/www/wsfed/sp/initSLO.php
+++ b/www/wsfed/sp/initSLO.php
@@ -13,7 +13,7 @@ if (!$config->getBoolean('enable.wsfed-sp', false))
 
 
 if (isset($_REQUEST['RelayState'])) {
-	$returnTo = $_REQUEST['RelayState'];
+	$returnTo = SimpleSAML_Utilities::checkURLAllowed($_REQUEST['RelayState']);
 } else {
 	throw new SimpleSAML_Error_Error('NORELAYSTATE');
 }
@@ -53,7 +53,7 @@ if (isset($session) ) {
 } else {
 
 	SimpleSAML_Logger::info('WS-Fed - SP.initSLO: User is already logged out. Go back to relaystate');
-	SimpleSAML_Utilities::redirectUntrustedURL($returnTo);
+	SimpleSAML_Utilities::redirectTrustedURL($returnTo);
 	
 }
 
diff --git a/www/wsfed/sp/initSSO.php b/www/wsfed/sp/initSSO.php
index 0e0b8613f..f3f1ec7d7 100644
--- a/www/wsfed/sp/initSSO.php
+++ b/www/wsfed/sp/initSSO.php
@@ -46,7 +46,7 @@ if ($idpentityid == null) {
 }
 
 try {
-	$relaystate = $_GET['RelayState'];
+	$relaystate = SimpleSAML_Utilities::checkURLAllowed($_GET['RelayState']);
 	
 	$idpmeta = $metadata->getMetaData($idpentityid, 'wsfed-idp-remote');
 	$spmeta = $metadata->getMetaData($spentityid, 'wsfed-sp-hosted');
-- 
GitLab