CVE-2024-22400 (published 2024-01-18T20:15Z)

Nextcloud User Saml is an app for authenticating Nextcloud users using SAML. In affected versions users can be given a link to the Nextcloud server and end up on a uncontrolled thirdparty server. It is recommended that the User Saml app is upgraded to version 5.1.5, 5.2.5, or 6.0.1. There are no known workarounds for this issue.

CVSS v3 base score: 6.1, MEDIUM (CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N)

Weakness information:
cwe_id: CWE-601
cwe_name: URL Redirection to Untrusted Site ('Open Redirect')
description: A web application accepts a user-controlled input that specifies a link to an external site, and uses that link in a Redirect. This simplifies phishing attacks.
extended_description: An http parameter may contain a URL value and could cause the web application to redirect the request to the specified URL. By modifying the URL value to a malicious site, an attacker may successfully launch a phishing scam and steal user credentials. Because the server name in the modified link is identical to the original site, phishing attempts have a more trustworthy appearance. Whether this issue poses a vulnerability will be subject to the intended behavior of the application. For example, a search engine might intentionally provide redirects to arbitrary URLs.
         

Fix information:
repo_url: https://github.com/nextcloud/user_saml
commit_details: repo_url: https://github.com/nextcloud/user_saml
author: Arthur Schiwon
author_date: 2023-11-28 12:11:08+01:00
msg: refactor(Controller): read parameter only once

Signed-off-by: Arthur Schiwon <blizzz@arthur-schiwon.de>
repository: repo_url: https://github.com/nextcloud/user_saml
repo_name: nextcloud/user_saml
description: :lock: App for authenticating Nextcloud users using SAML https://apps.nextcloud.com/apps/user_saml
repo_language: PHP
owner: nextcloud
file_changes: filename: SAMLController.php
change_type: ModificationType.MODIFY
diff: @@ -43,6 +43,7 @@ use OCP\ISession;
 use OCP\IURLGenerator;
 use OCP\IUserSession;
 use OCP\Security\ICrypto;
+use OCP\Security\ITrustedDomainHelper;
 use OCP\Server;
 use OneLogin\Saml2\Auth;
 use OneLogin\Saml2\Error;
@@ -77,6 +78,7 @@ class SAMLController extends Controller {
 	 * @var ICrypto
 	 */
 	private $crypto;
+	private ITrustedDomainHelper $trustedDomainHelper;
 
 	public function __construct(
 		string $appName,
@@ -91,7 +93,8 @@ class SAMLController extends Controller {
 		IL10N $l,
 		UserResolver $userResolver,
 		UserData $userData,
-		ICrypto $crypto
+		ICrypto $crypto,
+		ITrustedDomainHelper $trustedDomainHelper
 	) {
 		parent::__construct($appName, $request);
 		$this->session = $session;
@@ -105,6 +108,7 @@ class SAMLController extends Controller {
 		$this->userResolver = $userResolver;
 		$this->userData = $userData;
 		$this->crypto = $crypto;
+		$this->trustedDomainHelper = $trustedDomainHelper;
 	}
 
 	/**
@@ -192,11 +196,17 @@ class SAMLController extends Controller {
 	 * @throws \Exception
 	 */
 	public function login(int $idp = 1): Http\RedirectResponse {
+		$originalUrl = (string)$this->request->getParam('originalUrl', '');
+		if (!$this->trustedDomainHelper->isTrustedUrl($originalUrl)) {
+			$originalUrl = '';
+		}
+
 		$type = $this->config->getAppValue($this->appName, 'type');
 		switch ($type) {
 			case 'saml':
 				$auth = new Auth($this->samlSettings->getOneLoginSettingsArray($idp));
-				$returnUrl = $this->request->getParam('originalUrl', $this->urlGenerator->linkToRouteAbsolute('user_saml.SAML.login'));
+
+				$returnUrl = $originalUrl ?: $this->urlGenerator->linkToRouteAbsolute('user_saml.SAML.login');
 				$ssoUrl = $auth->login($returnUrl, [], false, false, true);
 				$response = new Http\RedirectResponse($ssoUrl);
 
@@ -215,7 +225,7 @@ class SAMLController extends Controller {
 				// Pack data as JSON so we can properly extract it later
 				$data = json_encode([
 					'AuthNRequestID' => $auth->getLastRequestID(),
-					'OriginalUrl' => $this->request->getParam('originalUrl', ''),
+					'OriginalUrl' => $originalUrl,
 					'Idp' => $idp,
 					'flow' => $flowData,
 				]);
@@ -229,7 +239,7 @@ class SAMLController extends Controller {
 				$response->addCookie('saml_data', $data, null, 'None');
 				break;
 			case 'environment-variable':
-				$ssoUrl = $this->request->getParam('originalUrl', '');
+				$ssoUrl = $originalUrl;
 				if (empty($ssoUrl)) {
 					$ssoUrl = $this->urlGenerator->getAbsoluteURL('/');
 				}

programming_language: PHP
filename: SAMLControllerTest.php
change_type: ModificationType.MODIFY
diff: @@ -39,6 +39,7 @@ use OCP\IURLGenerator;
 use OCP\IUser;
 use OCP\IUserSession;
 use OCP\Security\ICrypto;
+use OCP\Security\ITrustedDomainHelper;
 use PHPUnit\Framework\MockObject\MockObject;
 use Psr\Log\LoggerInterface;
 use Test\TestCase;
@@ -70,6 +71,7 @@ class SAMLControllerTest extends TestCase {
 	private $crypto;
 	/** @var SAMLController */
 	private $samlController;
+	private ITrustedDomainHelper|MockObject $trustedDomainController;
 
 	protected function setUp(): void {
 		parent::setUp();
@@ -86,6 +88,7 @@ class SAMLControllerTest extends TestCase {
 		$this->userResolver = $this->createMock(UserResolver::class);
 		$this->userData = $this->createMock(UserData::class);
 		$this->crypto = $this->createMock(ICrypto::class);
+		$this->trustedDomainController = $this->createMock(ITrustedDomainHelper::class);
 
 		$this->l->expects($this->any())->method('t')->willReturnCallback(
 			function ($param) {
@@ -111,7 +114,8 @@ class SAMLControllerTest extends TestCase {
 			$this->l,
 			$this->userResolver,
 			$this->userData,
-			$this->crypto
+			$this->crypto,
+			$this->trustedDomainController
 		);
 	}
 

programming_language: PHP