Unusual truncation flaw results in extremely long passwords being less secure
A recently patched truncation bug in the Node.js implementation of bcrypt resulted in inadequate encryption strength in certain use cases, according to a security advisory that was issued last week.
Node.js bcrypt is a popular hashing library with thousands of dependent packages and more than 500,000 weekly downloads.
The truncation bug caused very long inputs to be shortened to a few bytes, making the hashes extremely insecure. First reported in January, the vulnerability was patched in version 5.0.0 of the library.
According to its official NPM repository, the bcrypt package only hashes the first 72 bytes of its input string. Longer inputs are truncated to 72 bytes.
Since the string length is stored in a single-byte unsigned integer, this works fine as long as the input string is fewer than 255 bytes long.
But when the string length is 255 bytes or longer, it causes an integer overflow and wraps around the 0-255 range, restarting from zero.
This means that if a user provides, say, a 257-byte input password to bcrypt, it only hashes the first two bytes instead of truncating it to 72 bytes.
“This dramatically reduces brute-force complexity if the hash ever gets leaked,” Amitosh Swain, one of the maintainers of bcrypt for Node.js, told The Daily Swig.
“Normally it would have an N^72 complexity but with this bug, it will reduce to N^1 or N^2 depending on the length of the string.”
While users seldom provide passwords that are long enough to trigger the truncation error, if they use non-ASCII letters or emojis, each character can take up to four bytes.
Other factors can also cause bcrypt’s input to become very long.
“In the past, I have seen bug reports due to stupid implementation of bcrypt (like appending a static 128-bit salt to all passwords) which makes them a bit more vulnerable,” Swain says.
The researcher added that the bug is also likely to be triggered by developers who use bcrypt to hash and store API keys without being aware of the limits.
Legacy code comes back to haunt
“This is a bug that is present by virtue of bcrypt inheriting some of the original OpenBSD bcrypt code,” Swain said.
The buggy code was a holdover from the original implementation of bcrypt in the 90s. The bug was fixed in the 2014 version of OpenBSD bcrypt and given the $2b$ specifier. The old version was kept for backward compatibility as $2a$.
“In 2015-2016, we tried to port the same set of patches to the derived sources we use in node.bcrypt.js, but due to oversight we missed verifying if the tests were proper, and thus never fixed the buggy behavior,” Swain said.
“The code used to fall into the code path for 2a, even for the fixed version 2b.”
Upgrades not fast enough
The truncation bug was recorded in the National Vulnerability Database as CVE-2020-7689 with a base score of 7.5 (high) by NIST and 5.9 (medium) by Snyk.
“While Snyk’s assessment primarily revolved around password lengths, it’s possible that NVD considered the implications with multi-byte characters and the non-password usages of bcrypt,” Swain said.
“The difference in the scores is caused by us treating it as a high complexity attack, whereas NVD score treats this as a low complexity attack,” Adam Gold, security analyst at Snyk, told The Daily Swig.
“After revisiting this with another member of our team, we’ve decided to change the score to contain low complexity as well.”
The patched version of bcrypt was released in early July, but developers are not upgrading fast enough, according to Swain.
“The most popular version still is 1.0.3 and 3.0.6, released over four years ago,” he warned.
“There is a greater need in Node.js community to educate devs about the errors. Even after NPM warning about security vulnerabilities in the dependencies and warnings during installation, devs simply ignore them.”