Skip to content

Instantly share code, notes, and snippets.

@mozfreddyb
Created November 25, 2025 10:10
Show Gist options
  • Select an option

  • Save mozfreddyb/9bb187bfb1416610219b96a75bf3c811 to your computer and use it in GitHub Desktop.

Select an option

Save mozfreddyb/9bb187bfb1416610219b96a75bf3c811 to your computer and use it in GitHub Desktop.
testing setHTML in obscure DOM trees by going 3 levels deep and looping through all elements
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Sanitizer Fuzzing</title>
</head>
<body>
<h1>Testing...<span id="progress">0</span> of <span id="total">Unknown</span> (<span id="pct"></span>%)</h1>
<div id="ctx">...</div>
<script>
// from whatwg spec
let element_list = [
"a",
"abbr",
"address",
"area",
"article",
"aside",
"audio",
"b",
"base",
"bdi",
"bdo",
"blockquote",
"body",
"br",
"button",
"canvas",
"caption",
"cite",
"code",
"col",
"colgroup",
"data",
"datalist",
"dd",
"del",
"details",
"dfn",
"dialog",
"div",
"dl",
"dt",
"em",
"embed",
"fieldset",
"figcaption",
"figure",
"footer",
"form",
"h1",
"h2",
"h3",
"h4",
"h5",
"h6",
"head",
"header",
"hgroup",
"hr",
"html",
"i",
"iframe",
"img",
"input",
"ins",
"kbd",
"label",
"legend",
"li",
"link",
"main",
"map",
"mark",
"menu",
"meta",
"meter",
"nav",
"noscript",
"object",
"ol",
"optgroup",
"option",
"output",
"p",
"picture",
"pre",
"progress",
"q",
"rp",
"rt",
"ruby",
"s",
"samp",
"script",
"search",
"section",
"select",
"selectedcontent",
"slot",
"small",
"source",
"span",
"strong",
"style",
"sub",
"summary",
"sup",
"table",
"tbody",
"td",
"template",
"textarea",
"tfoot",
"th",
"thead",
"time",
"title",
"tr",
"track",
"u",
"ul",
"var",
"video",
"wbr"
];
// sourced from Sonar mxss cheat sheet
let vectors = [
`<svg></p><style><a id="</style><img src=1 onerror=alert(1)>">`,
`<form><math><mtext></form><form><mglyph><style></math><img src onerror=alert(1)>`,
`<math><mtext><table><mglyph><style><!--</style><img title="--&gt;&lt;/mglyph&gt;&lt;img&Tab;src=1&Tab;onerror=alert(1)&gt;">`,
'<math><mtext><table><mglyph><style><math><table id=”</table>”><img src onerror=alert(1)”>',
`<form><math><mtext></form><form><mglyph><svg><mtext><style><path id="</style><img onerror=alert(1) src>">`,
`<svg><xss><desc><noscript>&lt;/noscript>&lt;/desc>&lt;p>&lt;/p>&lt;style>&lt;a title="&lt;/style>&lt;img src onerror=alert(1)>">`,
`<svg><annotation-xml><foreignobject><style><!--</style><p id="--><img src='x' onerror='alert(1)'>">`,
`<svg><a><foreignobject><a><table><a></table><style><!--</style></svg><a id="-><img src onerror=alert(1)>">.`,
`<math><foo-test><mi><li><table><foo-test><li></li></foo-test>a<a><style><!--</style>a<foo-bar is="--><img src=x onerror=alert(1)>">`,
`<math><foo-test><mi><li><table><foo-test><li></li></foo-test><a><style><! \\$$\{</style>}<foo-b id="><img src onerror='alert(1)'>">hmm...</foo-b></a></table></li></mi></foo-test></math>`,
`<noscript><style></noscript><img src=x onerror=alert(1)>`,
`<svg><style><img src=x onerror=alert(1)>`,
`<math><p></p><style><!--</style><img src/onerror=alert(1)>--></style></math>`,
`<noscript><p title="</noscript><img src=x onerror=alert(1)>">`,
`<!--a foo=--!><img src=x onerror=alert(1)><!--<a>">`,
`<![CDATA[<math><img src=x onerror=alert(1)>]]>`
]
n = 506;
var payload = `${"<div>".repeat(n)}<table id="outer"><caption id="outer"><svg><desc><table id="inner"><caption id="inner"></caption></table></desc><style><a title="</style><img src onerror=alert(1)>"></a></style></svg></caption></table>${"</div>".repeat(n)}`;
vectors.push(payload);
results = [];
function myAlert() {
let stack = new Error().stack;
result = {
stack,
count,
outerHTML: ctx.outerHTML,
}
results.push(result);
}
var original_alert = alert;
window.alert = myAlert;
document.alert = myAlert;
let totalnum = Math.pow(element_list.length,3) * vectors.length;
total.textContent = totalnum;
var count = 0;
var o = 0;
var m = 0;
var i = 0;
let step = 1;
function next(o, step) {
if (step > element_list.length) {
ctx.textContent = "Done.";
let pretty = JSON.stringify(results, null, 2);
ctx.innerHTML += `<pre>${pretty}</pre>`;
return -1;
}
for (; o < step; o++) {
let outer = element_list[o];
console.log("Start", outer);
let outer_el = document.createElement(outer);
ctx.appendChild(outer_el);
for (var m=0; m < element_list.length; m++) {
let middle = element_list[m];
let middle_el = document.createElement(middle);
outer_el.appendChild(middle_el);
for (var i=0; i < element_list.length; i++) {
let inner = element_list[i];
let inner_el = document.createElement(inner);
middle_el.appendChild(inner_el);
for (let xss of vectors) {
inner_el.setHTML(xss, {sanitizer: {}});
++count;
}
}
middle_el.textContent = "";
}
outer_el.textContent = "";
progress.textContent = count;
pct.textContent = Math.round(count / totalnum * 100);
console.log("Done with", outer);
}
}
// based on local testing, iteration at 3 levels deep takes about 11 seconds
// continue the main loop every 15 seconds
let interval = setInterval(outer_loop, 15);
function outer_loop() {
r = next(o++, step++)
if (r === -1) {
clearInterval(interval);
}
}
</script>
<button onclick="next(o++, step++)">Next</button>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment