Bypassing Parentheses Filters: Template Literals to the Rescue

XSS Series - Part 5 Part 5 of 6

After exploring JSFuck and character-based filtering, you might think that filtering parentheses would prevent function calls. However, JavaScript provides alternative syntax for calling functions that doesn’t require parentheses. In this article, we’ll explore the “No Parentheses Challenge” and learn how template literals can bypass filters that remove parentheses.

The challenge we’ll be working with is easy02.php from xss.challenge.training.hacq.me. This challenge demonstrates that even when parentheses are filtered, JavaScript execution is still possible using template literal syntax.

The Challenge: No Parentheses

Challenge URL: easy02.php

Let’s examine what this challenge does:

<script src="hook.js"></script>
<?php
// you cannot do anything without ...

// no parentheses ...
$escaped = preg_replace("/[()]/", "", $_GET['payload']);

// no event handlers!
$escaped = preg_replace("/.*o.*n.*/i", "", $escaped);
?>

<h1>Hello, <?= $escaped ?>!</h1>

Understanding the Filters

The code applies two filters to the user input:

Filter 1: Remove Parentheses

$escaped = preg_replace("/[()]/", "", $_GET['payload']);

This regular expression /[()]/ matches:

  • Opening parentheses (
  • Closing parentheses )

Any parentheses in the input are removed, which would prevent traditional function calls like alert('XSS').

Filter 2: Remove Event Handlers

$escaped = preg_replace("/.*o.*n.*/i", "", $escaped);

This regular expression /.*o.*n.*/i matches:

  • Any sequence containing the letters “o” followed by “n” (case-insensitive)
  • This filters out event handlers like onclick, onerror, onload, onfocus, etc.

The i flag makes it case-insensitive, so it catches variations like OnClick, ONERROR, etc.

The Problem

At first glance, these filters seem effective:

  • No parentheses → Can’t call functions like alert('XSS')
  • No event handlers → Can’t use onerror, onload, etc.

However, JavaScript provides an alternative syntax for calling functions that doesn’t require parentheses: template literals.

Parentheses Filter Flow

The diagram above shows how traditional function calls with parentheses are blocked, but template literal calls pass through both filters.

What are Template Literals?

Template literals (also called template strings) are a JavaScript feature introduced in ES6. They use backticks (`) instead of single or double quotes, and allow embedded expressions.

Basic Template Literal Syntax

// Traditional string
const name = "World";
const greeting = "Hello, " + name + "!";

// Template literal
const greeting = `Hello, ${name}!`;

Template Literals as Function Arguments

Here’s the key feature that makes this bypass possible: You can call functions with template literals without using parentheses.

// Traditional function call
alert('XSS');

// Template literal call (equivalent)
alert`XSS`;

When you use a template literal directly after a function name (without parentheses), JavaScript treats it as a function call with the template literal as the first argument.

Template Literal Comparison

The diagram above illustrates the difference between traditional function calls (blocked) and template literal calls (allowed).

How It Works

When you write alertXSS“, JavaScript internally converts it to:

alert(['XSS'])

The template literal becomes an array containing the string parts. For a simple template literal like `XSS`, this is equivalent to ['XSS'], which is then passed as the first argument to alert().

The Solution

To solve the challenge, we use a template literal instead of parentheses:

<script>alert`XSS`</script>

Why This Works

  1. No parentheses: The template literal syntax doesn’t use () characters, so it passes Filter 1.

  2. No event handlers: We’re using a <script> tag with a direct function call, not an event handler, so it passes Filter 2.

  3. Valid JavaScript: alertXSS“ is valid JavaScript syntax that executes the alert function.

Complete Payload

The full payload for the challenge is:

<script>alert`XSS`</script>

When this is submitted:

  1. Filter 1 removes any () characters (none found) ✓
  2. Filter 2 removes any on* patterns (none found) ✓
  3. The payload passes through unchanged
  4. The browser executes <script>alertXSS</script>
  5. The alert is triggered!

Template Literals vs Traditional Function Calls

AspectTraditional CallTemplate Literal Call
Syntaxalert('XSS')alertXSS“
ParenthesesRequiredNot required
Filtered by /[()]/YesNo
FunctionalityIdenticalIdentical
Browser SupportAll browsersES6+ (all modern browsers)
Use CaseStandard function callsBypassing parentheses filters

When Template Literals Are Useful

Template literals are particularly useful for bypassing filters in these scenarios:

  1. Parentheses filters: When () characters are removed
  2. Strict syntax filters: When traditional function call syntax is blocked
  3. Multi-line payloads: Template literals support multi-line strings
  4. Expression embedding: Can embed expressions with ${} (though this requires more characters)

Advanced Template Literal Techniques

Multiple Arguments

While template literals primarily work with a single argument, you can still pass multiple arguments using other methods:

// Traditional
alert('Hello', 'World');

// Template literal (single argument)
alert`Hello World`;

// Multiple arguments with template literal (requires workaround)
[].constructor.alert`Hello``World`;

Tagged Template Literals

Template literals can also be used with “tagged” functions, which provides even more flexibility:

// Tagged template literal
String.raw`XSS`;

// This can be used to bypass certain filters

However, for the basic challenge, the simple alertXSS“ syntax is sufficient.

Security Lessons

This challenge teaches several important security lessons:

1. Syntax Filtering is Not Enough

Simply removing specific characters (like parentheses) doesn’t prevent JavaScript execution. Modern JavaScript provides multiple ways to achieve the same result.

Better Approach: Use proper output encoding based on context (HTML encoding, JavaScript encoding, etc.)

2. Multiple Filter Layers Can Still Fail

Even with two filters (parentheses and event handlers), the attack succeeds because:

  • The filters target specific attack vectors
  • They don’t account for alternative syntax
  • They focus on blocking rather than allowing safe content

Better Approach: Use allowlists (whitelists) instead of blocklists (blacklists)

3. Context-Aware Protection

The same filtering approach won’t work in all contexts. A filter that removes parentheses might seem effective, but it doesn’t account for:

  • Template literals
  • Other ES6+ features
  • Alternative function call syntax
  • Future JavaScript features

Better Approach: Use context-aware output encoding and Content Security Policy (CSP)

4. Defense in Depth

Relying on multiple filters that target specific characters is fragile. A more robust approach includes:

  • Input validation
  • Output encoding (context-aware)
  • Content Security Policy (CSP)
  • Proper use of secure APIs (textContent instead of innerHTML)
  • Regular security updates and testing

Filter Bypass Strategies Comparison

StrategyCharacters UsedUse CaseFilter BypassedDetection Difficulty
Template LiteralsBackticks (`)Parentheses filter/[()]/Low
JSFuck[]()!+Alphanumeric filter/[a-zA-Z0-9]/Medium
Event Handlersonerror, onloadScript tag filter<script> blockingLow
Unicode encodingVarious UnicodeCharacter restrictionsCharacter filtersLow
HTML entities&#xNN;HTML contextHTML encodingLow

Key Takeaways

  1. Parentheses filtering is insufficient: JavaScript provides alternative syntax for function calls.

  2. Template literals bypass parentheses filters: Using backticks instead of parentheses allows function calls without ().

  3. Multiple filters can still fail: Even with multiple layers, if they target specific syntax rather than the underlying behavior, they can be bypassed.

  4. Syntax evolves: Modern JavaScript (ES6+) introduces new features that can bypass older filtering techniques.

  5. Defense requires multiple layers: Don’t rely on character-based filtering alone.

  6. Context matters: Different contexts require different protection strategies.

  7. Allowlists over blocklists: Instead of blocking specific characters, allow only safe content.

Next Steps

Now that you understand how template literals can bypass parentheses filters, you can:

  1. Try the challenge yourself: easy02.php
  2. Experiment with other template literal variations
  3. Learn about other ES6+ features that might bypass filters
  4. Study Content Security Policy (CSP) as a more effective defense
  5. Practice with other filter bypass techniques

Remember: The goal isn’t just to bypass filters - it’s to understand why syntax-based filtering fails and how to implement proper security controls. Every bypass technique you learn helps you build more secure applications.

Related Articles