Skip to content
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

NativeEncoder::needsRehash passes string as 2nd argument to password_needs_rehash; this has to be an int #36451

Closed
olia-bn opened this issue Apr 14, 2020 · 19 comments

Comments

@olia-bn
Copy link

olia-bn commented Apr 14, 2020

Symfony version(s) affected: 4.4.7, others

Description
Symfony\Component\Security\Core\Encoder\NativePasswordEncoder::needsRehash is calling PHP native function password_needs_rehash with 2nd argument (algorithm) being string while it needs to be an int.

https://github.com/symfony/security-core/blob/7b409f4f186728081f37e9e788c1617dd2b6d702/Encoder/NativePasswordEncoder.php#L108

Same issue will affect NativePasswordEncoder::encodePassword function
https://github.com/symfony/security-core/blob/7b409f4f186728081f37e9e788c1617dd2b6d702/Encoder/NativePasswordEncoder.php#L71

How to reproduce
No special set up required; algorithm variable of the NativePasswordEncoder is a string and it's directly passed into password_needs_rehash

Possible Solution
Cast the variable to string before passing ?

@nicolas-grekas
Copy link
Member

Hi @olia-bn, actually, the expected type changed in PHP 7.4: it's a string now IIRC.

@olia-bn
Copy link
Author

olia-bn commented Apr 14, 2020

https://www.php.net/manual/en/function.password-needs-rehash.php explicitly defines it as an int, and there's nothing about type change for this function in change in change log https://www.php.net/manual/en/doc.changelog.php

@nicolas-grekas
Copy link
Member

nicolas-grekas commented Apr 14, 2020

I suppose that the doc is not up to date...
Here is the behavioral hint about this: https://3v4l.org/qnrM7

@wouterj
Copy link
Member

wouterj commented Apr 14, 2020

@nicolas-grekas is correct here, see this RFC that is marked as "implemented (php 7.4)": https://wiki.php.net/rfc/password_registry

Seems like, unless the user overwrites the setting, Symfony is using the constants: https://github.com/symfony/security-core/blob/7b409f4f186728081f37e9e788c1617dd2b6d702/Encoder/NativePasswordEncoder.php#L51

When extracting this, the code seems to work without errors: https://3v4l.org/7JGBn

@olia-bn did you get a PHP error while using this feature or how did you find it/why want you to change it?

@curry684
Copy link
Contributor

curry684 commented Apr 14, 2020

We've got a semi-old project that was originally started in the Symfony 3.x era. It explicitly defines a bcrypt encoder in the security config section, and we're running it on PHP 7.2. It died on a notice indicating 'suspicious string to number conversion' or something.

From what I can quickly see it's a rare combination of how bcrypt is translated internally to the NativePasswordEncoder, which barely anyone will run into as sodium/auto have been default for quite a while.

Seems like, unless the user overwrites the setting, Symfony is using the constants:

That line still casts to string, thus causing the issue on PHP<7.4.

@olia-bn
Copy link
Author

olia-bn commented Apr 14, 2020

@nicolas-grekas 👍
Time to fix PHP manual as well!

We are running it on 7.2.27

@wouterj I got a notice escalated to exception on dev environment:
Notice: A non well formed numeric value encountered

@wouterj
Copy link
Member

wouterj commented Apr 14, 2020

Hmm, @nicolas-grekas are you maybe aware of the reason why there is an explicit (string) typecast for $this->algo? Seems like we can solve the notice by just accepting int/string, whatever is contained in the constants.

Time to fix PHP manual as well!

@olia-bn are you any good with XML? 😛 https://github.com/php/doc-en/blob/master/reference/password/functions/password-needs-rehash.xml

@stof
Copy link
Member

stof commented Apr 14, 2020

The string cast looks wrong indeed, due to older versions having an integer.

@olia-bn
Copy link
Author

olia-bn commented Apr 15, 2020

I think @stof hits the nail on the head.

My suggestion to cart it to int before calling the native function was based on the (not up to date) documentation, but since the class itself is using PHP defined constants, simply removing the casting (and type hint in constructor) would solve the issue for all PHP versions

@wouterj
Copy link
Member

wouterj commented Apr 15, 2020

@olia-bn do you want to create a PR doing that in 4.4? :)

@curry684
Copy link
Contributor

curry684 commented Apr 15, 2020

Ok, I just started building the PR with @olia-bn and then we discovered it's not a real bug but a DX issue as I wrote a test to fail and it didn't at first.

The string cast is slightly suspicious for old PHP versions, but not a real issue as PHP will internally happily juggle a string with a "pure" integer back to an integer for BC reasons:

$ php7.2 -r "var_dump(password_hash('foo', (string) PASSWORD_BCRYPT));"
Command line code:1:
string(60) "$2y$10$7GuTSTVam05noCLFuVf1b.IT1Qw5BgRWJ84aE96.33amFwlvaIywG"

It WILL however give the seen notice when we explicitly feed it the PHP 7.4 constants:

$ php7.2 -r "var_dump(password_hash('foo', '2y'));"
PHP Notice:  A non well formed numeric value encountered in Command line code on line 1
PHP Stack trace:
PHP   1. {main}() Command line code:0
PHP   2. password_hash() Command line code:1
Command line code:1:
string(96) "$argon2i$v=19$m=65536,t=4,p=1$akIyZkNUS1RiN0ZNdUdjTA$/BcMwZshNRAkM5DMYsoefm4OKE8UVeLVVvO9D2QwxmA"

This got me puzzled as to why we would at some point be mixing those up, only to realize the code doesn't but the developer might accidentally do so (and did in this case).

Our development server has all PHP versions from 7.0 to 7.4 installed, and we select the right one via .htaccess statements. The CLI runs 7.4 by default. So when the developer updated the code, she ran the usual console commands and it rebuilt the container on 7.4, preparing the NativePasswordEncoder service with 7.4 constants.

And then when she logged in on the website, running 7.2, it crashed on instantiating the service to check for rehash.

So, the string cast is harmless as far as I'm concerned, and removing it won't fix this issue anyway (it would crash on 'unknown algorithm type 2y requested'). The question is now whether we should detect this situation as I do foresee it might crash some production websites in the world when a sysadmin forgets to clear/warmup caches while upgrading PHP on their server.

@curry684
Copy link
Contributor

curry684 commented Apr 15, 2020

Come to think of it - @nicolas-grekas shouldn't DI somehow safeguard against using a container compiled on a different PHP version anyway?

Appending PHP_VERSION_ID to the serialize call in ContainerBuilder::hash would pretty much solve this and all future similar problems. Could create some challenges for releasing sites with pre-built containers though.

@enumag
Copy link
Contributor

enumag commented May 13, 2020

I just got a Notice: A non well formed numeric value encountered. It seems to be caused by me accidentally using PHP 7.4 in CLI but 7.3 in Nginx.

@stof
Copy link
Member

stof commented May 13, 2020

Doing the cache warmup with a different PHP version that the PHP-FPM runtime using the cache is an unsupported use case.

@wouterj
Copy link
Member

wouterj commented May 13, 2020

Should we maybe do what @curry684 suggested: Append php version to the container hash to make sure this no longer happens (or add an exception if the version is not the same as the one building the container?)

@curry684
Copy link
Contributor

is an unsupported use case

But it's still happening a lot unintentionally, this specific change is just exposing it more painfully.

or add an exception if the version is not the same as the one building the container?

I don't think we want to let people accidentally kill production sites by running a console command.

I think embedding the major/minor version in the hash should prevent most issues, we'd just need to document it at https://symfony.com/doc/current/components/dependency_injection/workflow.html#working-with-a-cached-container

@nicolas-grekas
Copy link
Member

nicolas-grekas commented May 15, 2020

embedding the major/minor version in the hash

That would be quite confusing I fear. We'd better make the logic related to this issue able to cope with both type of values.

Up for a PR?

@curry684
Copy link
Contributor

I have no clue what you just said 😆

@nicolas-grekas
Copy link
Member

Check #36824

nicolas-grekas added a commit that referenced this issue May 16, 2020
… pre-PHP74 values of `PASSWORD_*` consts (nicolas-grekas)

This PR was merged into the 4.4 branch.

Discussion
----------

[Security/Core] fix compat of `NativePasswordEncoder` with pre-PHP74 values of `PASSWORD_*` consts

| Q             | A
| ------------- | ---
| Branch?       | 4.4
| Bug fix?      | yes
| New feature?  | no
| Deprecations? | no
| Tickets       | Fix #36451
| License       | MIT
| Doc PR        | -

Commits
-------

df32171 [Security/Core] fix compat of `NativePasswordEncoder` with pre-PHP74 values of `PASSWORD_*` consts
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

8 participants