[php-src] Issue #8397: crypt() accepts invalid characters in salt
| From: | martenlehmann | Date: | Mon, 18 Apr 2022 18:51:48 +0000 |
| Subject: | [php-src] Issue #8397: crypt() accepts invalid characters in salt | ||
| Groups: | php.bugs | ||
| Request: | Send a blank email to php-bugs+get-241058@lists.php.net to get a copy of this message | ||
Issue: https://github.com/php/php-src/issues/8397
Author: martenlehmann
### Description
The following code:
```php
<?php
printf(
"%s\n%s\n",
crypt('whatever', '$6$+=./CIKSWMxwyohL$'),
crypt('whatever', '$6$+=.äöü😀bCI$')
);
```
Resulted in this output:
```
$6$+=./CIKSWMxwyohL$sb5N9Nb5Bt2qDbLbOQ9RtoHxhC1K8w7ijWOzmio17b1CMLcjBGG3m8amcWHCoXJB0G25aZ4uutgnxjKbliUbQ/
$6$+=.äöü😀bCI$Xe9mv1hkb03sKiDi6JRdRbd02i0tz/46XpLPHoLB1pq0qSZV7To.NWmQieb7mA4eZ.kZBmfuCRJYlpDgi59Hu0
```
But I expected an exception or
*0 instead of this output, because both salts contain
invalid characters.
Background:
In our setup, PHP is used to create a hash, that will be distributed to and consumed by different
other services (OpenLDAP, Apache HTTP Server, Python Backends etc.). Therefore we cannot use the
latest hashing functions or algorithms, but the highest common denominator, which is the OS's
crypt() function with SHA512.
Up until (including) Ubuntu 18.04, the OS's crypt() supported salts including
+=./. Starting Ubuntu 20.04, the special characters have been limited to
./. However, PHP doesn't seem to care about that.
- In the first example, the salt contains +=./, which once worked, yet has later been
restricted.
- In the second example, the salt contains even an emoji, which wasn't allowed ever.
What's so bad about this?
There is no way to tell - using PHP - that the salts and generated hashes are invalid and will cause
verification problems in other systems. Even PHP's hash_equals() and
password_verify() functions have zero issue when verifying these hashes against the
original passwords. Unfortunately, all services consuming these hashes and relying on the
system's crypt() for verification do.
In Python, the following code (using the first password and salt as above) will return
*0:
```python
import crypt
crypt.crypt('whatever', '$6$+=./CIKSWMxwyohL$')
```
So does Perl:
```perl
printf(
"%s\n",
crypt('whatever', '$6$+=./CIKSWMxwyohL$')
);
```
The passlib library for Python will even raise a more explicit ValueError
exception: invalid characters in sha512_crypt salt
```python
from passlib.hash import sha512_crypt
sha512_crypt.verify('whatever',
'$6$+=./CIKSWMxwyohL$sb5N9Nb5Bt2qDbLbOQ9RtoHxhC1K8w7ijWOzmio17b1CMLcjBGG3m8amcWHCoXJB0G25aZ4uutgnxjKbliUbQ/')
```
[passlib](https://passlib.readthedocs.io/en/stable/lib/passlib.hash.sha256_crypt.html) documents the
allowed characters for the salt as follows: [./0-9A-Za-z]
Perldoc on [crypt()](https://perldoc.perl.org/functions/crypt) does it as well:
[./0-9A-Za-z]
So I see two issues:
1. As PHP doesn't provide a function to create a valid salt (like
crypt.mksalt(crypt.METHOD_SHA512) in Python), this step relies on the creativity of the
developer and since the allowed characters aren't well documented, there is a big chance that
invalid characters will be part of the salt and thus the hash.
2. As PHP doesn't seem to use the OS's crypt() and it's own
implementation doesn't seem to validate the input as strict, there is a big chance this happens
without noticing and unexpected errors in other systems will occur. A naive upgrade from Ubuntu
18.04 to 20.04 would have created tons of failed logins in our setup.
### PHP Version
PHP 7.4.3
### Operating System
Ubuntu 20.04