Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Arbitrary security restriction in UserBadge #49830

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Zuulka opened this issue Mar 27, 2023 · 8 comments
Closed

Arbitrary security restriction in UserBadge #49830

Zuulka opened this issue Mar 27, 2023 · 8 comments
Assignees

Comments

@Zuulka
Copy link

Zuulka commented Mar 27, 2023

Description

Hello there !

I'm opening a dedicated issue after my original comment here

A recent 6.2 update introduced a security test in UserBadge.

A new MAX_USERNAME_LENGTH constant, set to 4096, test the length of the user identifier to prevent session storage flooding.

The fact that the constant is arbitrary set to 4096 because "it should be more than enough for normal usages" is a problem, because it only relies on personal apreciation.

There should be a way, for people who know the risks, to easily override or bypass that restriction or to change the value of MAX_USERNAME_LENGTH, but the fact that it is a class constant accessed with the "self::" keyword makes it difficult.

I gave an example where this could lead to some blocking situation in my original comment here.

A long term solution could be to add a configuration parameter to properly override MAX_USERNAME_LENGTH if set.

A short term solution could be to, at least, use the "static::" keyword in the MAX_USERNAME_LENGTH test. In that way, it would be possible to extends UserBadge with a custom class, and redefine MAX_USERNAME_LENGTH inside the extending class.

Z.

Example

Short term proposition :

In UserBadge.

Change :

if (\strlen($userIdentifier) > self::MAX_USERNAME_LENGTH) {

    throw new BadCredentialsException('Username too long.');

}

For :

if (\strlen($userIdentifier) > static::MAX_USERNAME_LENGTH) {

    throw new BadCredentialsException('Username too long.');

}
@chalasr
Copy link
Member

chalasr commented Apr 1, 2023

I'm fine making it overridable by changing self to static, but I don't think it's worth exposing configuration for.
We may add some phpdoc on the constant telling about the issue regadding long passwords with session storages, and eventually mention the OWASP password storage cheatsheet for guidance.
PR welcome

@Spomky
Copy link
Contributor

Spomky commented Apr 1, 2023

For instance, with oauth2, the implementation of knpuniversity/oauth2-client-bundle which states in the documentation (here) to send your oauth2 access token directly as the user badge "identifier" :

return new SelfValidatingPassport(

new UserBadge($accessToken->getToken(), function() use ($accessToken, $client) {

[...]
Later, you're supposed to retrieve your identifier in a user provider or in the closure of user badge to construct your "user".

Now with SF6.2 and the access_token authentication method, you can directly have the real user identifier with almost no effort.
A possible AccessTokenHandler in your case could be as follows:

final class AccessTokenHandler implements AccessTokenHandlerInterface
{

    private $client;

    public function __construct(
        ClientRegistry $clientRegistry,
        private readonly EntityManagerInterface $entityManager,
    )
    {
        $this->client = $clientRegistry->getClient('facebook_main');
    }

    public function getUserBadgeFrom(#[\SensitiveParameter] string $accessToken): UserBadge
    {
        /** @var FacebookUser $facebookUser */
        $facebookUser = $this->client->fetchUserFromToken($accessToken);
        $email = $facebookUser->getEmail();

        // 1) have they logged in with Facebook before? Easy!
        $existingUser = $this->entityManager->getRepository(User::class)->findOneBy(['facebookId' => $facebookUser->getId()]);

        if ($existingUser) {
            return new UserBadge($existingUser->getIdentifier(), fn() => $existingUser);
        }

        // 2) do we have a matching user by email?
        $user = $this->entityManager->getRepository(User::class)->findOneBy(['email' => $email]);

        // 3) Maybe you just want to "register" them by creating
        // a User object
        $user->setFacebookId($facebookUser->getId());
        $this->entityManager->persist($user);
        $this->entityManager->flush();

        return new UserBadge($user->getIdentifier(), fn() => $user);
    }
}

@wouterj
Copy link
Member

wouterj commented Apr 1, 2023

Although the limit is indeed a bit arbitrary, I don't feel very comfortable about allowing users to override the length limit.

In the new security system, a lot more functionality can rely on the user identifier and unintentionally create a new risk for CVE-2016-4423. For instance, the login throttling feature also uses the user identifier in both the lock and cache storage.

Also, this patch must target 6.3, where we already have much better support for token-based user providers like @Spomky shows.

So I would say I would rather challenge code that requires long user identifiers, than allowing increasing the length.

@Zuulka
Copy link
Author

Zuulka commented Apr 3, 2023

Thanks everyone for reaching out !

@chalasr :

I'm fine making it overridable by changing self to static, but I don't think it's worth exposing configuration for.

I'm good with that !

@wouterj
Would you be ok with the 'static' solution as a compromise ?
That would allow a clean way to do it code-wise.

@Spomky :
Looks interesting, thanks a lot for your detailed answer, I'll definitely take the time to try that approach !
Is that already a thing with 6.2 ? Or is it a future 6.3 feature as hinted by @wouterj answer ?

@derrabus
Copy link
Member

derrabus commented Apr 3, 2023

Although the limit is indeed a bit arbitrary, I don't feel very comfortable about allowing users to override the length limit.

Me neither.

I don't believe, UserBage was ever meant to hold a full JWT token payload instead of a user identifier. So everyone who did that must've known that they had implemented a dirty hack.

We should rather provide an alternative (if there isn't already one) than making that constant overridable.

@Spomky
Copy link
Contributor

Spomky commented Apr 4, 2023

Is that already a thing with 6.2

Yes, I mentioned it in #49830 (comment)
No need for any modification except the knpuniversity/oauth2-client-bundle documentation to take into account the access token feature

@wouterj
Copy link
Member

wouterj commented Aug 10, 2023

Let's close here, as it seems to be agreed upon that this does not need fixing on the Symfony side.

@wouterj wouterj closed this as completed Aug 10, 2023
@chalasr
Copy link
Member

chalasr commented Aug 10, 2023

Indeed 👍

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

6 participants