LiquidJS replace_first Filter Exponential Memory Amplification DoS
The `replace_first` filter in LiquidJS is vulnerable to exponential memory amplification due to its use of JavaScript's `String.prototype.replace()` and mishandling of the `$&` backreference pattern, allowing attackers to bypass the `memoryLimit` and cause denial of service.
LiquidJS version 10.24.0 and earlier contains a vulnerability in its replace_first filter that allows for exponential memory amplification. The replace_first filter delegates to JavaScript’s native String.prototype.replace(), which interprets $& as a backreference to the matched substring. The filter only charges the input string length against the configured memoryLimit, not the amplified output. An attacker can exploit this by crafting a Liquid template with a replacement string containing multiple repetitions of $&, causing the output string to grow exponentially with each replacement. By chaining this technique across multiple variable assignments, an attacker can easily exhaust available memory, leading to a denial-of-service condition. This vulnerability affects applications that render user-provided Liquid templates, such as CMS platforms, newsletter editors, and SaaS platforms.
Attack Chain
- The attacker crafts a malicious Liquid template.
- The template uses the
replace_firstfilter with a pattern containing multiple$&backreferences. For example:{% assign s = "A" %}{% assign s = s | replace_first: s, "$&$&$&...(50 times)...$&" %}. - The LiquidJS engine parses the template.
- The
replace_firstfilter is called. - The filter utilizes the native
String.prototype.replace()method to perform the replacement. - Each instance of
$&in the replacement string is expanded to the matched substring, causing the output string to grow exponentially. - The expanded string consumes excessive memory, potentially exceeding available resources.
- The application crashes or becomes unresponsive, resulting in a denial-of-service condition.
Impact
Successful exploitation of this vulnerability can lead to a denial-of-service condition. A single request can allocate hundreds of megabytes of memory, and concurrent requests can cause complete service unavailability. The Node.js event loop is blocked, and legitimate user requests are stalled. Empirical results have demonstrated that with 20 concurrent requests, legitimate users experience up to 13-second delays. Each attack request costs only a few hundred bytes, making it easy to launch a large-scale attack.
Recommendation
- Apply a patch to LiquidJS that properly accounts for memory usage when using the
replace_firstfilter with backreferences. - Alternatively, disable or remove the
replace_firstfilter entirely and use thereplacefilter instead, which treats$&as a literal string. - Implement input validation and sanitization to prevent the use of
$&backreferences in user-provided Liquid templates. - Monitor web server logs for suspicious requests containing Liquid templates with excessive use of the
replace_firstfilter and$&patterns using the Sigma rule below. - Implement rate limiting to mitigate the impact of denial-of-service attacks.
- Increase the
memoryLimitconfiguration value to provide a temporary buffer against memory exhaustion, but this will not fully prevent the attack.
Detection coverage 2
Detect LiquidJS replace_first Memory Amplification Attempt
highDetects suspicious HTTP requests that attempt to exploit the LiquidJS replace_first memory amplification vulnerability by looking for multiple $& sequences in the template data.
Detect LiquidJS replace_first with large number of $& repetitions
highDetects HTTP requests with LiquidJS templates using replace_first filter and a large number of $& repetitions, indicating a potential memory amplification attack.
Detection queries are kept inside the platform. Get full rules →