CVE-2023-39343 (PHP, published 2023-08-04T01:15Z)
Description: value: 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. 


Severity: nan (CVSS v3 base score: 4.3)
Weakness: CWE-204 \u2014 Observable Response Discrepancy
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.
Repository: https://github.com/sulu/sulu
Files changed:
config/packages/security.yaml
src/Sulu/Bundle/SecurityBundle/Security/AuthenticationHandler.php
src/Sulu/Bundle/SecurityBundle/Tests/Application/config/config.yml
src/Sulu/Bundle/SecurityBundle/Tests/Functional/Security/AuthenticationHandlerTest.php
Commit message: 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

Vulnerable code:
        - { path: ^/admin/logout, role: PUBLIC_ACCESS }
        - { path: ^/admin/_profiler, role: PUBLIC_ACCESS }
        - { path: ^/admin/2fa, role: PUBLIC_ACCESS }
            $array = ['message' => $exception->getMessage()];
        - { path: ^/admin/login, roles: PUBLIC_ACCESS }
        - { path: ^/admin/2fa, role: PUBLIC_ACCESS }
            form_login:
                path: /admin/logout
                target: /admin/
<?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());
    }
}

Fixed code:
        - { path: ^/admin/2fa, roles: PUBLIC_ACCESS }
        - { path: ^/admin/_profiler, roles: PUBLIC_ACCESS }
            $array = ['message' => $exception->getMessageKey()];
        - { 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 }
            json_login:
                path: sulu_admin.logout
<?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);
    }
}