diff --git a/domdig.js b/domdig.js index a102071..f3d6778 100755 --- a/domdig.js +++ b/domdig.js @@ -29,12 +29,23 @@ function getUrlMutations(url, payload){ } async function scanAttributes(crawler){ - const elems = await crawler.page().$$('[xssSinkAttribute^="window.___xssSink"]'); - for(let e of elems){ - // must use evaluate since puppetteer cannot get non-standard attributes - let attr = await e.evaluate(i => i.getAttribute("xssSinkAttribute")); - let key = attr.match(/\(([0-9]+)\)/)[1]; - utils.addVulnerability(PAYLOADMAP[key], VULNSJAR, null, VERBOSE); + // use also 'srcdoc' since it can contain also esacped html: + // content can have a "timer" so maybe is not executed in time + const attrs = ["href", "action", "formaction", "srcdoc", "content"]; + for(let attr of attrs){ + const elems = await crawler.page().$$(`[${attr}]`); + for(let e of elems){ + // must use evaluate since puppetteer cannot get non-standard attributes + let val = await e.evaluate( (i,a) => i.getAttribute(a), attr); + if(val.match("___xssSink") == null){ + continue; + } else { + let key = val.match(/\(([0-9]+)\)/)[1]; + let es = await utils.getElementSelector(e); + utils.addVulnerability(PAYLOADMAP[key], VULNSJAR, null, VERBOSE, `Attribute '${attr}' of '${es}' set to payload`); + break; + } + } } } @@ -194,6 +205,7 @@ function ps(message){ crawler = await loadCrawler(targetUrl.href, payload, options, true); await scanDom(crawler, options); await triggerOnpaste(crawler); + await scanAttributes(crawler); await close(crawler); ps(cnt + "/" + payloads.length + " payloads checked"); cnt++; @@ -212,6 +224,7 @@ function ps(message){ } else { await scanDom(crawler, options); } + await scanAttributes(crawler); await triggerOnpaste(crawler); await close(crawler); ps(cnt + "/" + payloads.length + " payloads checked"); @@ -227,7 +240,7 @@ function ps(message){ console.log(utils.prettifyJson(VULNSJAR)); } else if(VERBOSE){ for(let v of VULNSJAR){ - utils.printVulnerability(v[0], v[1], v[2]); + utils.printVulnerability(v[0], v[1], v[2], v[3]); } } diff --git a/utils.js b/utils.js index a812c0f..917a218 100755 --- a/utils.js +++ b/utils.js @@ -13,22 +13,25 @@ exports.writeJSON = writeJSON; exports.prettifyJson = prettifyJson; exports.loadPayloadsFromFile = loadPayloadsFromFile; exports.error = error; +exports.getElementSelector = getElementSelector; -function addVulnerability(vuln, jar, url, verbose){ +function addVulnerability(vuln, jar, url, verbose, message){ + message = message || null; p = vuln.payload.replace("window.___xssSink({0})", "alert(1)"); for(let e of jar){ if(e[0] == p && e[1] == vuln.element && (!url || e[2] == url)){ return; } } - jar.push([p, vuln.element, url]); + jar.push([p, vuln.element, url, message]); if(verbose){ - printVulnerability(p, vuln.element, url); + printVulnerability(p, vuln.element, url, message); } } -function printVulnerability(payload, element, url){ - var msg = chalk.red('[!]') + ` DOM XSS found: ${element} → ${payload}`; +function printVulnerability(payload, element, url, message){ + message = message || "DOM XSS found"; + var msg = chalk.red('[!]') + ` ${message}: ${element} → ${payload}`; if(url){ msg += " → " + url; } @@ -252,3 +255,36 @@ function prettifyJson(obj, layer){ } return obj; } + + +async function getElementSelector(element){ + return await element.evaluate( i => { + function gs(element){ + if(!element || !(element instanceof HTMLElement)) + return ""; + var name = element.nodeName.toLowerCase(); + var ret = []; + var selector = "" + var id = element.getAttribute("id"); + + if(id && id.match(/^[a-z][a-z0-9\-_:\.]*$/i)){ + selector = "#" + id; + } else { + let p = element; + let cnt = 1; + while(p = p.previousSibling){ + if(p instanceof HTMLElement && p.nodeName.toLowerCase() == name){ + cnt++; + } + } + selector = name + (cnt > 1 ? `:nth-of-type(${cnt})` : ""); + if(element != document.documentElement && name != "body" && element.parentNode){ + ret.push(gs(element.parentNode)); + } + } + ret.push(selector); + return ret.join(" > "); + } + return gs(i); + }); +} \ No newline at end of file