اللهم صلِّ وسلّم وبارك على سيدنا محمد وعلى آله وصحبه أجمعين، عدد ما أحاط به علمك وخط به قلمك وأحصاه كتابك

While testing an e-commerce platform, I discovered a stored XSS vulnerability through a custom template injection that completely bypassed Cloudflare WAF protection. What made this finding interesting was that the payload wasn't executing where I injected it — it was silently rendering somewhere else entirely.

Q: What is Template Injection?

A: Template Injection occurs when user input is embedded into a template engine and evaluated as code instead of being treated as plain text. This can lead to information disclosure, XSS, or even remote code execution depending on the engine and context.

— - The Recon — Fuzzing Everything

It started like any normal testing session. I was poking at an e-commerce web application — the kind that has a support chat, user profiles, friend lists, and product reviews. I decided to fuzz every single input field across the application with a classic polyglot payload:

#^<&'"-

Most fields returned nothing interesting. Just normal sanitized responses or generic errors. But then I hit the address field and got something I didn't expect.

Instead of sanitizing or rejecting my input, the application leaked a raw template rendering error:

%% error %%
 %% getCurrentAddress.billing_name %%
 %% getCurrentAddress.billing_address | decode_htmlentities %%
 %% getCurrentAddress.billing_country | decode_htmlentities %%
 +%% parseFloat(getVatRate(getCurrentCountryCode)) + '% '
 %% VAT%%
None

Now THIS was interesting. The application was leaking internal template syntax, variable names, and even helper functions. I immediately noticed the %% … %% delimiters — this wasn't Jinja2, Twig, Mako, or any known template engine. This was a custom proprietary template engine.

The Wall — Cloudflare WAF

Knowing the application sat behind Cloudflare WAF, I realized that standard SSTI payloads would never make it through:

{{7*7}} ← blocked ${7*7} ← blocked <%= 7*7 %> ← blocked

But here's the thing — WAFs detect known template syntax patterns. They don't know what %%..%% means because it's not in their rulebooks. So I decided to use the application's own syntax against it.

I injected %%7*7%% into every input field across the application — name, address, bio, comment fields — one payload per field.

Sent the requests. Checked the responses. Nothing. No 49. No execution. No nothing.

I tried variations. Different fields. Different endpoints. Still nothing.

AHH..😤

I kept at it for two full days. Nothing worked. I was convinced the template engine wasn't actually evaluating user input, and the error I saw was just a debug leak with no real exploitation path.

I gave up.

The Breakthrough — It Was There All Along

Two days later, I opened a support ticket for something unrelated. And that's when I saw it.

My display name was showing as 49.

None

Wait. What?

I rushed to the settings page and checked — the name field still had %%7*7%% stored in it. The template engine had executed 7*7=49. The injection was real. It just wasn't rendering where I injected it — it was rendering in a completely different context.

The name field was being evaluated through the template engine whenever it was displayed: in support tickets, friend lists, user profiles. I was looking for output in the wrong place the entire time.

The custom %%..%% syntax had bypassed Cloudflare completely because the WAF had no rules to detect it.

Escalation — From SSTI to XSS

Now I knew the injection was real. Time to escalate.

I injected:

%%self%%

The output:

[object Window]
None

This confirmed I had access to the JavaScript global scope. The template engine was evaluating expressions client-side — this was Client-Side Template Injection (CSTI), which meant I could potentially reach XSS.

Final payload:

%% alert('XSS') %%

None

BOOOOOOOM. 💥

JavaScript executed successfully. The alert() fired wherever my name was rendered — and that was a LOT of places.

The Impact — Stored XSS Everywhere

This wasn't just a self-XSS. This was a stored XSS affecting multiple high-impact surfaces:

  • User profile pages — anyone visiting your profile triggered the payload — Friend lists — adding someone as a friend executed the payload on their profile — Support tickets — support agents viewing tickets would trigger execution — Product page comments — the same vulnerability existed in product review/comment sections
None
💸💸

Why Cloudflare Didn't Catch It:

Cloudflare WAF uses signature-based detection for known template syntax ({{, ${, <%). The custom %%..%% delimiters simply didn't match any existing WAF rules. This is a classic example of why proprietary implementations often undermine standardized security controls.