Description
Summary
This report details multiple Regular Expression Denial of Service (ReDoS) vulnerabilities found in the rimraf-standalone.js
script within the Hyper terminal repository. Specific regular expressions used for parsing glob patterns and comments are susceptible to catastrophic backtracking when processing maliciously crafted input strings. This can lead to excessive CPU consumption, effectively causing a denial of service.
This advisory provides proof-of-concept attack strings for each vulnerability and proposes fixes using lookaheads to mitigate the ReDoS risk.
Vulnerabilities Details
- ReDoS in brace expansion parsing
- File:
hyper/bin/rimraf-standalone.js
Line 563 in 2a7bb18
- Vulnerable Regex:
/,.*\}/
- Attack String: An attack can be triggered by a long string of commas without a closing brace.
""+",".repeat(100000)+"\u0000"
// or
""+",".repeat(100000)+"\n@"
- Explanation: The
.*
component is greedy and is followed by,
. When the}
is not present in the input string, the regex engine will match all characters until the end of the string with.*
. It will then backtrack one character at a time to try to find a match for}
. With a sufficiently long input string, this backtracking process can consume excessive CPU resources, leading to a denial of service. - Proposed Fix: The vulnerability can be remediated by replacing the greedy
.*
with a more constrained pattern that uses a negative lookahead. This ensures that the engine doesn't greedily consume past a potential closing brace. - Original Regex:
/,.*\}/
- Fixed Regex:
/,(?:(?!}).)*}/
- How the Fix Works: The pattern
(?:(?!}).)*
is a "tempered greedy token". At each position, the negative lookahead(?!})
asserts that the next character is not a}
before the.
matches it. This prevents the catastrophic backtracking because the engine will fail immediately if it encounters a}
where it is not supposed to, and it doesn't need to backtrack if the final}
is missing.
- ReDoS in pattern matching
- File:
hyper/bin/rimraf-standalone.js
Line 902 in 2a7bb18
- Vulnerable Code:
if (!pattern.match(/\{.*\}/)) {
- Vulnerable Regex:
/\{.*\}/
Attack String: An attack can be triggered by a long string of opening braces without a closing brace.
""+"{".repeat(100000)+"\u0000"
// or
""+"{".repeat(100000)+"\n@"
- Explanation: This vulnerability is functionally identical to the first one. The greedy
.*1 will cause the regex engine to backtrack catastrophically when trying to match a long string that starts with
{but never has a corresponding
}`. - Proposed Fix: Similar to the previous vulnerability, we can use a negative lookahead to temper the greedy
.*
. - Original Regex:
/\{.*\}/
- Fixed Regex:
/\{(?:(?!}).)*}/
- How the Fix Works: The logic is the same as the first fix. The
(?:(?!}).)*
construct ensures linear time matching by checking for the absence of}
at each step, thus avoiding the exponential backtracking that occurs with.*
on malicious input.
- ReDoS in globstar pattern replacement
- File:
hyper/bin/rimraf-standalone.js
Line 1681 in 2a7bb18
- Vulnerable Code:
var gpattern = pattern.replace(/(\/\*\*)+$/, '');
- Vulnerable Regex:
/(\/\*\*)+$/
- Attack String: An attack can be triggered by a long string of
/**
sequences that is not at the end of the string.
""+"/**".repeat(100000)+"◎"
- Explanation: The vulnerability lies in the combination of a repeating capturing group
(\/\*\*)+
and the end-of-string anchor$
. When the input string contains many repetitions of/**
but does not end with it (due to the trailing◎
), the regex engine will backtrack. It will try every possible combination of how(\/\*\*)+
could match the preceding sequence, leading to an exponential number of steps and causing the application to hang. - Proposed Fix: This type of ReDoS can be mitigated by emulating an atomic group using a lookahead. This prevents the engine from backtracking into the repeated group.
- Original Regex:
/(\/\*\*)+$/
- Fixed Regex:
/(?=((\/\*\*)+$))\1/
- How the Fix Works: This fix uses a positive lookahead to achieve the desired behavior without catastrophic backtracking.
(?=((\/\*\*)+$))
is a lookahead that greedily matches the entire sequence of trailing/**
groups until the end of the string. This match is "atomic" or "possessive" in that the lookahead itself will not backtrack internally in a way that causes the ReDoS. The result of this match is captured in group 1.
\
1 is a backreference to the string captured by group 1. The engine then performs a single, literal match of this captured string. Because the computationally expensive part is done within the lookahead, and the main regex only performs a literal match, the catastrophic backtracking is prevented.
Recommendation
It is highly recommended to update the vulnerable regular expressions with the proposed fixes to prevent potential Denial of Service attacks. When crafting regular expressions, it is crucial to avoid patterns that can lead to catastrophic backtracking, such as using greedy quantifiers (*, +) on complex, repeating groups or on expressions that can have many permutations to match a given string.