Stored/persistent cross-site scripting

What is stored/persistent cross-site scripting?

Stored cross-site scripting, also called persistent cross-site scripting, is a specific type of cross-site scripting (XSS) vulnerability. The terms stored or persistent mean that 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 of its web pages.

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.