CONTENTS
Attack Chain
untrusted JSON → lodash importsKeys → Function(paramList) → comma-split signature → missing args → default param executes
Prerequisites, What you need to understand before reading
If you don’t understand these, the exploit will look like black magic.
- Concept 1: How
new Function(arg1, arg2, body)builds a function (arguments before the body are parameter names). - Concept 2: JavaScript coercion: arrays become comma-joined strings
(["a","b"] → "a,b"). - Concept 3: ES6 default parameters execute only when the argument is
undefined. - Tooling:
curl, basic Node/Express reading, and understanding JSON object keys.
Intro: What this challenge teaches
- Goal: turn a “harmless” JSON field into code execution during template compilation.
- Main idea: JSON keys are data… until a JS engine parses them as a function signature.
- Key lesson: when user-controlled strings reach compiler-like sinks (
Function,eval, template compilation), think structure injection, not string injection.
Code Analysis
High-level architecture
- Endpoint:
POST /render - Stack: Express + Consolidate + Lodash templates
- Trust boundary: attacker controls template options via
req.body.
The vulnerable path
The entire bug reduces to one bad trust decision:
Conceptual lodash behavior:
Compiles template
Fatal assumption: importsKeys are trusted variable names.
Why this works?
The bug exists because Lodash treats imports keys as variable names, while JavaScript treats function parameter lists as raw strings. When a comma-containing key crosses this boundary, a single JSON key expands into multiple parameters. Lodash supplies values positionally, not semantically, so excess parameters become undefined. In ES6, undefined is not a failure state, it is an execution trigger. so Function() parameter list is just a string
But arrays are coerced:
The JS engine only sees commas.
One JSON key Leads to multiple JS parameters
Lodash sees:
importsKeys = ["left, right"];
importsValues = ["VALUE"];
Compiled function
- function anonymous(left, right)
Mapping:
| Parameter | Value | Result |
|---|---|---|
| left | VALUE | filled |
| right | undefined | gap |
ES6 default parameters execute on undefined
We’re not injecting into the template body. We’re executing code during argument initialization.
Exploit Strategie I used
Let injected values get consumed:
Mapping:
| Param | Value | Result |
|---|---|---|
| a | X | filled |
| b | lodash object | swallowed |
| c | undefined | executes |
Why I chose this chain?
-
No quote breaking
-
No template body injection
-
No escaping
-
Pure structure abuse
That’s why the name “Gap” is perfect.
My working payload
Key properties
Cause:
- One JSON key → multiple JS parameters
- Lodash binds arguments positionally
Effect:
- Values run out
- Missing parameters become undefined
- Default expressions execute → RCE
Solve Analysis, What happened on the server?
-
Express parses JSON → req.body
-
Renderer forwards attacker-controlled imports
-
Lodash extracts keys (comma-containing string)
-
Function() builds a multi-parameter signature
-
Values run out → gap
-
Default parameter executes → RCE
Any system that forwards user-controlled strings into function signatures, argument lists, or compiler-like constructs becomes vulnerable to alignment attacks. The exploit does not rely on JavaScript, it relies on positional binding and default evaluation semantics.
Real-World Fix. How to prevent this Defensive rules?
-
Never pass user input as template options
-
Allowlist locals explicitly
-
Define helper imports server-side only
Minimal correct fix
If helpers are needed
Closing
One JSON key becomes many JS parameters, the values run out, and undefined turns into execution.