The abbreviation XSS stands for Cross-Site Scripting. Without going too deeply into the details, the essence of the attack is the injection of malicious code into a webpage. The attack does not affect the server side, but it directly impacts the client side — the users of the vulnerable service.
What kind of code can be injected into a page? After all, a page does not have access to a database or other server-side components where user data could be obtained. In this case, we are talking about malicious JavaScript code.
As you already know, in order to execute JavaScript code on a page (in the frontend context), it must be placed between the <script></script> tags. We discussed this method in the first chapter of the tutorial. This is exactly what an XSS attack relies on. An attacker injects malicious JavaScript code into the page. When a user visits this page, the code is executed and the user's confidential data is sent to the attacker.
How XSS Works
In practice, it may look like this. Imagine a personal blog where all registered users can leave comments on posts.
Everything works perfectly until a user appears who does not just want to discuss posts but wants to steal access to other users’ accounts. To do this, the attacker writes some text in the comment field, adds a <script> tag, and includes a small piece of JavaScript code inside it. If no filtering occurs, then after publishing such a comment, the code will be executed for all users who visit the page.
What could be dangerous about this code? Developers often use cookies to store a user's session identifier. What is an identifier? It is a string with a unique set of characters that allows the system to distinguish one authenticated user from another.
Malicious code can read this value and send it to the attacker’s server using an AJAX request. This is the essence of an XSS attack. After collecting such identifiers, the attacker can substitute them for their own use. If the developer has not implemented additional protection, the attacker will be able to log into the application using another user’s account.
The scenario described above is one of the most common, but it is not the only one. Without proper filtering, it is possible to execute any JavaScript code. For example, an attacker could collect all the data from the page (using simple querySelector methods) or perform other destructive actions.
XSS Example
Let’s try to look at the problem from a practical perspective. We will create a demonstration application and use a real example to understand what XSS means. As a demo application, we will make a simple form for entering a name. The user enters their name, submits the form, and a greeting message appears in a paragraph.
The application is extremely simple, but it clearly illustrates the problem.
<form id="basic-form">
<input id="user-name" type="text">
<input type="submit" value="Отправить" placeholder="Пожалуйста, представьтесь">
<p id="welcome-user"></p>
</form>
<script>
'use strict';
const submitFormHandler = (evt) => {
evt.preventDefault();
welcomeUserElement.innerHTML = Привет, ${userNameElement.value}!;
}
const formElement = document.querySelector(#basic-form);
const welcomeUserElement = formElement.querySelector(#welcome-user);
const userNameElement = formElement.querySelector(#user-name);
formElement.addEventListener(submit, submitFormHandler);
</script>
Try opening the page in a browser and testing the application. Make sure everything works as intended.
As a test, we entered the name “Igor”, and it was displayed in the paragraph. The application works as expected. Now, instead of a name, let’s enter a seemingly harmless string:
<img src="" onerror="alert('xss')">
Click the “Submit” button. This time, the paragraph #welcome-user will display “Hello, !”, but in addition to that you will see a modal window with the text “xss.” We have injected third-party JavaScript code into the page. In this example, it only shows a useless modal window, but you understand that it could do much more. Earlier we discussed several negative scenarios.
Why This Happens
The vulnerability occurs because the application inserts user input into the page using innerHTML without any validation or sanitization. When innerHTML is used, the browser interprets the inserted string as HTML markup, not just plain text. As a result, any HTML tags or JavaScript event handlers included in the input will be executed by the browser.
In the example above, the attacker inserted the following code:
<img src="" onerror="alert('xss')">
When the browser tries to load the image and fails, the onerror event handler is triggered, executing the JavaScript code inside it.
This demonstrates a classic DOM-based XSS vulnerability.
How to Prevent XSS
There are several important practices developers should follow to prevent XSS attacks.
1. Avoid Using innerHTML With Untrusted Input
Instead of inserting user input with innerHTML, use safer alternatives such as textContent or innerText.
Example:
welcomeUserElement.textContent = Hello, ${userNameElement.value}!;
In this case, the browser treats the value as plain text and does not interpret it as HTML.
2. Validate and Sanitize Input
User input should always be validated and sanitized before being processed or displayed. This includes:
Removing or escaping HTML tags
Filtering dangerous attributes like onerror, onclick, etc.
Escaping special characters (<, >, ", ')
Libraries such as DOMPurify are commonly used to sanitize HTML safely.
3. Use Content Security Policy (CSP)
A Content Security Policy is an additional security layer that helps prevent XSS by restricting which scripts can run in the browser.
Example HTTP header:
Content-Security-Policy: default-src 'self'; script-src 'self'
This prevents execution of inline scripts and scripts loaded from unknown sources.
4. Mark Cookies as HttpOnly
Since many XSS attacks attempt to steal session cookies, developers should mark cookies with the HttpOnly flag. This prevents JavaScript from accessing them.
Example:
Set-Cookie: sessionId=abc123; HttpOnly; Secure
Even if an attacker injects JavaScript into the page, they will not be able to read the cookie value.
Conclusion
XSS vulnerabilities occur when applications trust user input and insert it into web pages without proper handling. Even a small mistake — like using innerHTML instead of textContent — can lead to serious security risks.
To protect applications, developers should always treat user input as untrusted, properly sanitize data, avoid unsafe DOM APIs, and implement additional security mechanisms such as CSP and HttpOnly cookies.
Following these practices significantly reduces the risk of XSS attacks and helps keep user data secure.
What kind of code can be injected into a page? After all, a page does not have access to a database or other server-side components where user data could be obtained. In this case, we are talking about malicious JavaScript code.
As you already know, in order to execute JavaScript code on a page (in the frontend context), it must be placed between the <script></script> tags. We discussed this method in the first chapter of the tutorial. This is exactly what an XSS attack relies on. An attacker injects malicious JavaScript code into the page. When a user visits this page, the code is executed and the user's confidential data is sent to the attacker.
How XSS Works
In practice, it may look like this. Imagine a personal blog where all registered users can leave comments on posts.
Everything works perfectly until a user appears who does not just want to discuss posts but wants to steal access to other users’ accounts. To do this, the attacker writes some text in the comment field, adds a <script> tag, and includes a small piece of JavaScript code inside it. If no filtering occurs, then after publishing such a comment, the code will be executed for all users who visit the page.
What could be dangerous about this code? Developers often use cookies to store a user's session identifier. What is an identifier? It is a string with a unique set of characters that allows the system to distinguish one authenticated user from another.
Malicious code can read this value and send it to the attacker’s server using an AJAX request. This is the essence of an XSS attack. After collecting such identifiers, the attacker can substitute them for their own use. If the developer has not implemented additional protection, the attacker will be able to log into the application using another user’s account.
The scenario described above is one of the most common, but it is not the only one. Without proper filtering, it is possible to execute any JavaScript code. For example, an attacker could collect all the data from the page (using simple querySelector methods) or perform other destructive actions.
XSS Example
Let’s try to look at the problem from a practical perspective. We will create a demonstration application and use a real example to understand what XSS means. As a demo application, we will make a simple form for entering a name. The user enters their name, submits the form, and a greeting message appears in a paragraph.
The application is extremely simple, but it clearly illustrates the problem.
<form id="basic-form">
<input id="user-name" type="text">
<input type="submit" value="Отправить" placeholder="Пожалуйста, представьтесь">
<p id="welcome-user"></p>
</form>
<script>
'use strict';
const submitFormHandler = (evt) => {
evt.preventDefault();
welcomeUserElement.innerHTML = Привет, ${userNameElement.value}!;
}
const formElement = document.querySelector(#basic-form);
const welcomeUserElement = formElement.querySelector(#welcome-user);
const userNameElement = formElement.querySelector(#user-name);
formElement.addEventListener(submit, submitFormHandler);
</script>
Try opening the page in a browser and testing the application. Make sure everything works as intended.
As a test, we entered the name “Igor”, and it was displayed in the paragraph. The application works as expected. Now, instead of a name, let’s enter a seemingly harmless string:
<img src="" onerror="alert('xss')">
Click the “Submit” button. This time, the paragraph #welcome-user will display “Hello, !”, but in addition to that you will see a modal window with the text “xss.” We have injected third-party JavaScript code into the page. In this example, it only shows a useless modal window, but you understand that it could do much more. Earlier we discussed several negative scenarios.
Why This Happens
The vulnerability occurs because the application inserts user input into the page using innerHTML without any validation or sanitization. When innerHTML is used, the browser interprets the inserted string as HTML markup, not just plain text. As a result, any HTML tags or JavaScript event handlers included in the input will be executed by the browser.
In the example above, the attacker inserted the following code:
<img src="" onerror="alert('xss')">
When the browser tries to load the image and fails, the onerror event handler is triggered, executing the JavaScript code inside it.
This demonstrates a classic DOM-based XSS vulnerability.
How to Prevent XSS
There are several important practices developers should follow to prevent XSS attacks.
1. Avoid Using innerHTML With Untrusted Input
Instead of inserting user input with innerHTML, use safer alternatives such as textContent or innerText.
Example:
welcomeUserElement.textContent = Hello, ${userNameElement.value}!;
In this case, the browser treats the value as plain text and does not interpret it as HTML.
2. Validate and Sanitize Input
User input should always be validated and sanitized before being processed or displayed. This includes:
Removing or escaping HTML tags
Filtering dangerous attributes like onerror, onclick, etc.
Escaping special characters (<, >, ", ')
Libraries such as DOMPurify are commonly used to sanitize HTML safely.
3. Use Content Security Policy (CSP)
A Content Security Policy is an additional security layer that helps prevent XSS by restricting which scripts can run in the browser.
Example HTTP header:
Content-Security-Policy: default-src 'self'; script-src 'self'
This prevents execution of inline scripts and scripts loaded from unknown sources.
4. Mark Cookies as HttpOnly
Since many XSS attacks attempt to steal session cookies, developers should mark cookies with the HttpOnly flag. This prevents JavaScript from accessing them.
Example:
Set-Cookie: sessionId=abc123; HttpOnly; Secure
Even if an attacker injects JavaScript into the page, they will not be able to read the cookie value.
Conclusion
XSS vulnerabilities occur when applications trust user input and insert it into web pages without proper handling. Even a small mistake — like using innerHTML instead of textContent — can lead to serious security risks.
To protect applications, developers should always treat user input as untrusted, properly sanitize data, avoid unsafe DOM APIs, and implement additional security mechanisms such as CSP and HttpOnly cookies.
Following these practices significantly reduces the risk of XSS attacks and helps keep user data secure.