I am working on a Symfony 3.4
based projects which uses a WSSE authentication as described in the Symfony docs.
Each nonce is stored as separate file in the cache dir myProject/var/cache/prod/security/nonces
. Probelm is, that this dir becomes very, very large in size. The project has been up and running and the nonces already use almost 20GB in disk space!
$ cd myProject/var/cache/prod/security/
$ du -sch *
19G nonces
19G total
This seems pretty much to me... I tried to figure out how many nonces are stored and used the following command to count the files:
$ cd myProject/var/cache/prod/security/nonces
$ find -maxdepth 1 -type f | wc -l
4697417
Even for 4.7 million files 19GB seems pretty much. Each file would need to have a size of roughly about 4KB. However, as far as I can tell each file has only 10B...
$ cd myProject/var/cache/prod/security/nonces
$ ls -lh
-rw-r----- 1 user nobody 10 Jul 25 16:46 'MWFiYWM5YjAiOTRyOWRmZA=='
-rw-r----- 1 user nobody 10 Jul 1 19:41 'MWFiYWNiYTflNTdhLGYwYQ=='
-rw-r----- 1 user nobody 10 Sep 29 11:05 'MWFiYWNkNzEjZfFlCjM0OQ=='
...
I know that there is a difference between file size and consumed diskspace. However, du
also shows 10B of disk space:
$ du -sb --apparent-size MWFiYWNkNzEjZfFlCjM0OQ==
10
So, how can the files use 19G of disk space while each files only uses 10B? Am I missing something? Or did I not use the commands correctly?
Isn't there a better to store the nonces?
Of course I could delete the cache every now and then. However, this would make the nonces pretty much useless, wouldn't it?
du
reports the size of disk space that is consumed. Disk space is allocated in blocks. So the minimal space a file can occupy is 1 block. In your case the block size of your filesystem seems to be 4kb. Therefore ~4.7 million files with a size of 10byte consume 4700000 * 4kb, thats arround 19gb.
Nonces are usually cached for a few minutes. The symfony cookbook you mentioned recommends a nonce ttl of 5 minutes. Here is an extract of that documentation
class WsseProvider implements AuthenticationProviderInterface
{
protected function validateDigest($digest, $nonce, $created, $secret)
{
// Check created time is not in the future
if (strtotime($created) > time()) {
return false;
}
// Expire timestamp after 5 minutes
if (time() - strtotime($created) > 300) {
return false;
}
// Try to fetch the cache item from pool
$cacheItem = $this->cachePool->getItem(md5($nonce));
// Validate that the nonce is *not* in cache
// if it is, this could be a replay attack
if ($cacheItem->isHit()) {
// In a real world application you should throw a custom
// exception extending the AuthenticationException
throw new AuthenticationException('Previously used nonce detected');
}
// Store the item in cache for 5 minutes
$cacheItem->set(null)->expiresAfter(300);
$this->cachePool->save($cacheItem);
// Validate Secret
$expected = base64_encode(sha1(base64_decode($nonce).$created.$secret, true));
return hash_equals($expected, $digest);
}
}
The nonces are added to the cache-pool with a ttl of 5 minutes.
Keeping the nonces longer than the time you consider the created field valid (five minutes in this example if (time() - strtotime($created) > 300)
) doesn't add any extra security, because as soon as the creation date becomes outdated a replayed request can be rejected based on the created timestamp.