Stored/persistent cross-site scripting

What is stored/persistent cross-site scripting?

Stored cross-site scripting is a type of cross-site scripting (XSS) where the attacker first sends the payload to the web application, then the application saves (i.e. stores/persists) the payload (for example, in a database or server-side text files), and finally, the application unintentionally executes the payload for every victim visiting its web pages. Stored cross-site scripting is also called persistent cross-site scripting.

Example of stored/persistent cross-site scripting

In this example, the developer wants to include a simple comment section on one of their pages (page.php) without deploying a full CMS such as WordPress. They include the following form on the page.php web page:

<form action="/page.php" method="post" id="comment">
  <label for="name">Your name:</label>
  <input type="text" id="name" name="name">
  <label for "comment">Your comment:</label>
  <textarea id="comment" name="comment" rows="5" cols="30"></textarea>
  <button type="submit" form="comment" value="comment">Add a comment</button>
</form>

The page.php file includes the following code:

// Add a new comment into the database using PDO to avoid SQL injection
(...)
$name=$_POST["name"];
$comment=$_POST["comment"];
$sql = "INSERT INTO comments (name, comment) VALUES (?,?)";
$statement = $pdo->prepare($sql);
$statement->execute([$name, $comment]);
(...)
// Display existing comments
$comments = $db->query('SELECT * FROM comments')->fetchAll();
foreach($comments as $comment) {
    echo "<tr><td>".$comment['name']."</td>";
    echo "<td>".$comment['comment']."</td></tr>";
}
(...)

As you can see, the application inserts the comment into the database without any validation or sanitization and later displays it on the same page for other users, again with no validation or sanitization.

The stored cross-site scripting attack

The attacker enters the following comment into the form, leaving the name empty:

<script>alert("LEAVE THIS PAGE! YOU ARE BEING HACKED!");</script>

The comment is saved into the database. From now on, when any user visits the page, their browser interprets the following code:

<tr><td></td><td><script>alert("LEAVE THIS PAGE! YOU ARE BEING HACKED!");</script></td></tr>

The browser finds a <script> tag and executes the JavaScript code within it. As a result, it displays a pop-up window for the user urging them to leave the page.

The consequences in this fairly innocent example are that users, afraid for their safety, will stop visiting the page until the administrator is notified and removes malicious content from the database.

The fix

The developer, informed of the vulnerability, decides to use HTMLPurifier filtering to safeguard the code and, additionally, escape HTML characters. They import the HTMLPurifier library and modify the page.php file in the following way:

// Add a new comment into the database using PDO to avoid SQL injection
// and HTMLPurifier with HTML escaping to avoid XSS
(...)
$name=$_POST["name"];
$comment=$_POST["comment"];
// Purify user data using HTMLPurifier
(...)
$purifier = new HTMLPurifier($config);
$purified_name = $purifier->purify($name);
$purified_comment = $purifier->purify($comment);
// Just to be sure, HTML-escape special characters
$safe_name = htmlspecialchars($purified_name, ENT_QUOTES);
$safe_comment = htmlspecialchars($purified_comment, ENT_QUOTES);
// Save safe data in the database
$sql = "INSERT INTO comments (name, comment) VALUES (?,?)";
$statement = $pdo->prepare($sql);
$statement->execute([$safe_name, $safe_comment]);
(...)

Consequences of stored/persistent cross-site scripting attacks

Stored cross-site scripting is the most dangerous of all XSS types simply because it reaches the largest number of users. Such an attack may have all of the consequences that we listed in the general section dedicated to cross-site scripting.

Here are some actions that a black-hat hacker could perform based on the simple example presented earlier:

  • They could redirect users to a malicious page that mimics the original application and ask them to log in, thus stealing their credentials.
  • They could steal the users’ session cookies and use them to impersonate the users in the original web application.
  • They could trick users into downloading and installing malware on their computers, for example, trojans, cryptocurrency miners, or ransomware.

Note that if your web application serves internal users, such as company employees, a successful attack could allow malicious actors to escalate and possibly gain access to other computer systems in your organization.

How to prevent stored/persistent cross-site scripting vulnerabilities

To prevent stored/persistent cross-site scripting, follow our overall guidelines for preventing cross-site scripting, listed in the general section dedicated to cross-site scripting.

Frequently asked questions

What is stored cross-site scripting?

In a stored cross-site scripting attack, the attacker first sends the payload to the web application, then the application saves the payload, and finally, the application unintentionally executes the payload for every victim visiting its web pages.

 

Read more about cross-site scripting (XSS) in general.

What is an example of stored cross-site scripting?

The most famous example of stored cross-site scripting was the 2005 Samy worm, which was a simple prank of a MySpace user. Within 20 hours, the XSS worm reached over one million victims. For his prank, the culprit was sentenced to three years probation with no access to the Internet.

 

Read more about the famous 2005 Samy worm.

How to prevent stored cross-site scripting?

The best way to avoid cross-site scripting vulnerabilities is through validation and sanitization of user input. There are two primary techniques that you can use to sanitize data coming from the user: filtering and escaping.

 

Find out about the potential consequences of cross-site scripting attacks.


Written by: Tomasz Andrzej Nidecki, reviewed by: Benjamin Daniel Mussler