Cross-Site Scripting (XSS) is a browser-side code injection attack. An injection attack is performed when the attacker is able to inject malicious code into an application. This code is then executed by the particular environment and performs malicious actions.
In the case of the browser, the attacker is injecting malicious scripts inside of a web app, which is being used by the victim. In general, XSS attacks are based on the victim’s trust in the legitimacy of the web app they use.
Depending on the goals of the attacker, XSS can be implemented in a number of different ways. There are five main types of XSS attacks.
Persistent (Stored) XSS
Persistent XSS is possible when a web app takes user input and stores it into its servers. When the application doesn’t perform proper front-end and back-end validations before storing the data, it exposes serious vulnerabilities. When the web app loads the stored data afterward and embeds it into the HTML response pages is the moment when a potential code injection is possible.
These types of attacks are less frequent because the vulnerabilities that make them possible are less common and difficult to find.
On the other hand, they are highly impactful. The reason is that once the malicious data is being stored on the web app’s servers, it can potentially be served to many users. The users don’t need to click on malicious links or anything else — the malicious code is already embedded in the app itself.
Persistent XSS attacks are Type 2 XSS attacks because the attack is carried out via two requests:
- Injecting malicious code and storing it on the web server.
- Loading the stored code and embedding it into the HTML pages that contain the payload.
Certain types of websites and web apps are more prone to the vulnerabilities that are required by Persistent XSS attacks because they allow users to share content. Common examples are social networks and forums.
Suppose that an attacker identifies vulnerabilities in the comment functionality under a post in a social network. The vulnerability is that the social network renders the raw input from the comments inside of the HTML on the page.
This allows the attacker to add a custom script into his comment:
When the social network loads this comment, it will include the `script` tag into its HTML. This will automatically redirect the current user to the URL of the malicious website and will send all of the cookies as a query parameter. The malicious website can then store the cookies and steal sensitive data.
Since many people can visit the comment section of a particular post, all of them will be victims to the attacker.
This particular case is very superficial, and there hopefully aren’t any serious social networks out there with such easy to find vulnerabilities, but it illustrates the potential scale of Persistent XSS.
The most effective way to prevent Persistent XSS attacks is to make sure that all user input is properly sanitized before it is being stored on the servers.
Sanitizing static content is also a great practice since malicious scripts can be injected in various ways.
There is almost nothing reliable you can do purely on the client-side to prevent Persistent XSS attacks.
Reflected XSS attacks occur when the data, which is sent from the browser to the server is contained in the servers’ response.
These attacks are called “reflected” since malicious scripts are reflected off of a web app to the victim’s browser.
The script is activated through a link, which sends a request to the web app with a vulnerability that enables the execution of malicious scripts.
Unlike Persistent XSS attacks, where the malicious script is being stored on the servers of the web app, Reflected XSS attacks only require that the malicious script is embedded into the URL.
While Persistent XSS attacks perform the malicious scripts automatically to any user, who visits a page with such a script, the Reflected XSS attacks require the end-user to click on the malicious link.
Suppose that a web app has search functionality. This is how the web app works:
- The user types a search term into an input field.
- The web app redirects the user to a “results” page which has the search term as a query parameter.
- The search term is taken from the query parameter and is being sent to the server for processing.
- When the processing is complete, the response is rendered on the page. The response contains both the search term of the user and the matched results from the server.
If the web app doesn’t perform any processing or validation on the search terms, the attacker can embed malicious code into the search term by providing the following input:
The generated URL will be:
And the page will then render:
If this URL is spread to other users, the supplied script by the attacker will execute in their browser.
There are various means by which an attacker can spread the malicious URL to victims. These include placing links on websites controlled by the attacker, or websites that allow content to be generated (forums, social networks, etc.), sending the link in an email, etc. The attack could target a particular user or it can be an indiscriminate attack against any user.
Unlike Persistent XSS attacks, users can avoid Reflected XSS attacks by being vigilant.
Specifically, this means not clicking on suspicious links that may contain malicious code.
As with Persistent XSS, sanitizing all of the user input is mandatory for the prevention of Reflected XSS.
Self-XSS is quite similar to Reflected XSS. The difference is that Self-XSS cannot be triggered via a specially crafted URL. Self-XSS can only be triggered by the victim themselves in their own browser.
This might sound like Self-XSS attacks are not dangerous. This is far from true. Self-XSS attacks are successfully delivered through social engineering. In the context of information security, social engineering is the psychological manipulation of people into performing actions that will potentially have negative consequences for them.
A common approach to Self-XSS attacks is making the victim paste some malicious code into their browser’s console. This gives the attacker access to all of the currently available information from the cookies, DOM, etc.
Self-XSS attacks can only be prevented by user vigilance. The developer of the web app cannot detect or block the execution of malicious code in the browser’s console.
Some popular web apps and websites are putting warning messages in the browser’s console to prevent users from executing any code there.
Browser vendors have also taken steps to mitigate this attack by implementing safeguards to warn users about Self-XSS attacks.
DOM-based XSS attacks are performed when the DOM of a web app is dynamically modified and malicious code is injected by the web app itself during runtime.
Let’s take a look at the following page:
The script in the page takes the value from the `role` query param and inserts it into the DOM.
The attacker can set the value of the query parameter to a malicious code, which will be injected into the DOM:
Even though the code of the web app is vulnerable to this attack, in this particular case, the server can detect it since the URL is part of the request. If there are security mechanisms built into the server, the attack will fail.
The technique to avoid sending the payload to the server is using URL fragments. This is the part of the URL after the `#` symbol. URL fragments are not sent to the server by the browser.
The previous URL can be modified to:
This way, the malicious script won’t reach the servers and it won’t be detected.
All of the DOM manipulation and redirects which depend on user input should be sanitized. The sanitization should happen on the client-side since DOM-based XSS cannot be prevented on the server.
In some cases, user vigilance can also play a role in this type of XSS attack. In cases similar to our example, where the user has to click a URL or they need to enter some data, they should be cautious about entering malicious code.
Blind XSS attacks are a type of Persistent XSS attacks. They are executed in the same manner.
The difference is that the malicious code is rendered and executed in another part of the application or in a completely different application. In both cases, the attacker has no access to the page which will execute the malicious code.
An example of a Blind XSS attack is when an attacker injects malicious code into a customer feedback page of a web app. When the web application’s administrator opens the feedback dashboard, the malicious code will be executed. This can even be in a different application, such as an internal tool for managing user feedback.
The attacker’s malicious code can be saved by the server and only executed after a long period of time when the administrator visits the vulnerable dashboard page. It can take hours, days, or even weeks until the payload is executed.
Another example of Blind XSS attacks is with logging solutions such as exception handlers. The attacker can use the API of the logger, to log some malicious code instead of an error. In the dashboard of the exception handling solution, where the logged errors are displayed, the malicious code will be rendered and executed.
Since Blind XSS attacks are a subset of Persistent XSS attacks, the preventing methodologies are the same.
There is a free trial if you’d like to give SessionStack a try.
If you missed the previous chapters of the series, you can find them here:
- An overview of the engine, the runtime, and the call stack
- Inside Google’s V8 engine + 5 tips on how to write optimized code
- Memory management + how to handle 4 common memory leaks
- The event loop and the rise of Async programming + 5 ways to better coding with async/await
- Deep dive into WebSockets and HTTP/2 with SSE + how to pick the right path
- The building blocks of Web Workers + 5 cases when you should use them
- Service Workers, their life-cycle, and use cases
- The mechanics of Web Push Notifications
- Tracking changes in the DOM using MutationObserver
- The rendering engine and tips to optimize its performance
- Inside the Networking Layer + How to Optimize Its Performance and Security
- Under the hood of CSS and JS animations + how to optimize their performance
- Parsing, Abstract Syntax Trees (ASTs) + 5 tips on how to minimize parse time
- The internals of classes and inheritance + transpiling in Babel and TypeScript
- Storage engines + how to choose the proper storage API
- The internals of Shadow DOM + how to build self-contained components
- WebRTC and the mechanics of peer to peer connectivity
- Under the hood of custom elements + Best practices on building reusable components
- Exceptions + best practices for synchronous and asynchronous code