CVE-2023-39343 (published 2023-08-04T01:15Z)

Sulu is an open-source PHP content management system based on the Symfony framework. It allows over the Admin Login form to detect which user (username, email) exists and which one do not exist. Sulu Installation not using the old Symfony 5.4 security System and previous version are not impacted by this Security issue. The vulnerability has been patched in version 2.5.10. \n\n

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

Weakness information:
cwe_id: CWE-204
cwe_name: Observable Response Discrepancy
description: The product provides different responses to incoming requests in a way that reveals internal state information to an unauthorized actor outside of the intended control sphere.
extended_description: This issue frequently occurs during authentication, where a difference in failed-login messages could allow an attacker to determine if the username is valid or not. These exposures can be inadvertent (bug) or intentional (design).
         

Fix information:
repo_url: https://github.com/sulu/sulu
commit_details: repo_url: https://github.com/sulu/sulu
author: Alexander Schranz
author_date: 2023-08-03 20:02:42+02:00
msg: Merge pull request from GHSA-wmwf-49vv-p3mr

* Fix AuthenticationHandler return inner exception message

* Add funcctional test case for AuthenticationHandlerTest

* Sync security bundle and sulu skeleton security config

* Remove not longer used LoginControllerTest via form_login

* Add test for success and fail login
repository: repo_url: https://github.com/sulu/sulu
repo_name: sulu/sulu
description: Core framework that implements the functionality of the Sulu content management system
repo_language: PHP
owner: sulu
file_changes: filename: security.yaml
change_type: ModificationType.MODIFY
diff: @@ -20,14 +20,13 @@ security:
         - { path: ^/admin/reset, roles: PUBLIC_ACCESS }
         - { path: ^/admin/security/reset, roles: PUBLIC_ACCESS }
         - { path: ^/admin/login$, roles: PUBLIC_ACCESS }
+        - { path: ^/admin/2fa, roles: PUBLIC_ACCESS }
         - { path: ^/admin/_wdt, roles: PUBLIC_ACCESS }
+        - { path: ^/admin/_profiler, roles: PUBLIC_ACCESS }
         - { path: ^/admin/translations, roles: PUBLIC_ACCESS }
         - { path: ^/admin$, roles: PUBLIC_ACCESS }
         - { path: ^/admin/$, roles: PUBLIC_ACCESS }
         - { path: ^/admin/p/, roles: PUBLIC_ACCESS }
-        - { path: ^/admin/logout, role: PUBLIC_ACCESS }
-        - { path: ^/admin/_profiler, role: PUBLIC_ACCESS }
-        - { path: ^/admin/2fa, role: PUBLIC_ACCESS }
         - { path: ^/admin, roles: ROLE_USER }
 
     firewalls:

programming_language: Markdown
filename: AuthenticationHandler.php
change_type: ModificationType.MODIFY
diff: @@ -104,7 +104,7 @@ class AuthenticationHandler implements AuthenticationSuccessHandlerInterface, Au
     {
         if ($request->isXmlHttpRequest()) {
             // if AJAX login
-            $array = ['message' => $exception->getMessage()];
+            $array = ['message' => $exception->getMessageKey()];
             $response = new JsonResponse($array, 401);
         } else {
             // if form login

programming_language: PHP
filename: config.yml
change_type: ModificationType.MODIFY
diff: @@ -8,8 +8,14 @@ security:
     access_control:
         - { path: ^/admin/reset, roles: PUBLIC_ACCESS }
         - { path: ^/admin/security/reset, roles: PUBLIC_ACCESS }
-        - { path: ^/admin/login, roles: PUBLIC_ACCESS }
-        - { path: ^/admin/2fa, role: PUBLIC_ACCESS }
+        - { path: ^/admin/login$, roles: PUBLIC_ACCESS }
+        - { path: ^/admin/2fa, roles: PUBLIC_ACCESS }
+        - { path: ^/admin/_wdt, roles: PUBLIC_ACCESS }
+        - { path: ^/admin/_profiler, roles: PUBLIC_ACCESS }
+        - { path: ^/admin/translations, roles: PUBLIC_ACCESS }
+        - { path: ^/admin$, roles: PUBLIC_ACCESS }
+        - { path: ^/admin/$, roles: PUBLIC_ACCESS }
+        - { path: ^/admin/p/, roles: PUBLIC_ACCESS }
         - { path: ^/admin, roles: ROLE_USER }
 
     firewalls:
@@ -17,13 +23,12 @@ security:
             pattern: ^/
             lazy: true
             entry_point: sulu_security.authentication_entry_point
-            form_login:
+            json_login:
                 check_path: sulu_admin.login_check
                 success_handler: sulu_security.authentication_handler
                 failure_handler: sulu_security.authentication_handler
             logout:
-                path: /admin/logout
-                target: /admin/
+                path: sulu_admin.logout
             two_factor:
                 prepare_on_login: true
                 prepare_on_access_denied: true

programming_language: Ruby
filename: LoginControllerTest.php
change_type: ModificationType.DELETE
diff: @@ -1,29 +0,0 @@
-<?php
-
-/*
- * This file is part of Sulu.
- *
- * (c) Sulu GmbH
- *
- * This source file is subject to the MIT license that is bundled
- * with this source code in the file LICENSE.
- */
-
-namespace Sulu\Bundle\AdminBundle\Tests\Functional\Controller;
-
-use Sulu\Bundle\TestBundle\Testing\SuluTestCase;
-
-class LoginControllerTest extends SuluTestCase
-{
-    public function testFalseLoginRedirect(): void
-    {
-        $client = $this->createClient();
-        $client->request('POST', '/admin/login', [
-            '_username' => 'FalseUser',
-            '_password' => 'FalsePassword',
-        ]);
-        $response = $client->getResponse();
-        $this->assertHttpStatusCode(302, $response);
-        $this->assertSame('/admin/', $response->getTargetUrl());
-    }
-}

programming_language: unknown
filename: AuthenticationHandlerTest.php
change_type: ModificationType.ADD
diff: @@ -0,0 +1,82 @@
+<?php
+
+/*
+ * This file is part of Sulu.
+ *
+ * (c) Sulu GmbH
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace Sulu\Bundle\SecurityBundle\Tests\Functional\Security;
+
+use Sulu\Bundle\TestBundle\Testing\SuluTestCase;
+
+class AuthenticationHandlerTest extends SuluTestCase
+{
+    public function testLoginFail(): void
+    {
+        $client = $this->createClient([], [
+            'CONTENT_TYPE' => 'application/json',
+            'HTTP_ACCEPT' => 'application/json',
+            'HTTP_X-Requested-With' => 'XMLHttpRequest',
+        ]);
+
+        $client->request('POST', '/admin/login', [], [], [], '{"username": "not-existing-user", "password": "wrong"}');
+
+        $response = $client->getResponse();
+        $this->assertHttpStatusCode(401, $response);
+        $notExistUserContent = $response->getContent();
+
+        $this->assertSame('{"message":"Invalid credentials."}', $notExistUserContent);
+    }
+
+    public function testLoginSuccess(): void
+    {
+        $client = $this->createClient([], [
+            'CONTENT_TYPE' => 'application/json',
+            'HTTP_ACCEPT' => 'application/json',
+            'HTTP_X-Requested-With' => 'XMLHttpRequest',
+        ]);
+
+        $testUser = $this->getTestUser();
+
+        $client->request('POST', '/admin/login', [], [], [], '{"username": "' . $testUser->getUsername() . '", "password": "test"}');
+
+        $response = $client->getResponse();
+        $this->assertHttpStatusCode(200, $response);
+        $notExistUserContent = $response->getContent();
+
+        $this->assertSame(
+            '{"url":"\/admin\/","username":"test","completed":true,"twoFactorMethods":["trusted_devices"]}',
+            $notExistUserContent
+        );
+    }
+
+    public function testLoginFailExistUserHasSameMessageAsNotExist(): void
+    {
+        $client = $this->createClient([], [
+            'CONTENT_TYPE' => 'application/json',
+            'HTTP_ACCEPT' => 'application/json',
+            'HTTP_X-Requested-With' => 'XMLHttpRequest',
+        ]);
+
+        $testUser = $this->getTestUser();
+
+        $client->request('POST', '/admin/login', [], [], [], '{"username": "not-existing-user", "password": "wrong"}');
+
+        $response = $client->getResponse();
+        $this->assertHttpStatusCode(401, $response);
+        $notExistUserContent = $response->getContent();
+
+        $client->request('POST', '/admin/login', [], [], [], '{"username": "' . $testUser->getUsername() . '", "password": "wrong"}');
+
+        $response = $client->getResponse();
+        $this->assertHttpStatusCode(401, $response);
+        $existUserContent = $response->getContent();
+
+        $this->assertSame($notExistUserContent, $existUserContent);
+        $this->assertSame('{"message":"Invalid credentials."}', $notExistUserContent);
+    }
+}

programming_language: PHP