Skip to content

Commit

Permalink
better stored detection
Browse files Browse the repository at this point in the history
  • Loading branch information
fcavallarin committed Nov 6, 2019
1 parent b6bdd37 commit 11e43ca
Show file tree
Hide file tree
Showing 3 changed files with 136 additions and 67 deletions.
8 changes: 8 additions & 0 deletions consts.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
exports.CHECKTYPE_DOM = "dom";
exports.CHECKTYPE_REFLECTED = "reflected";
exports.CHECKTYPE_STORED = "stored";
exports.VULNTYPE_DOM = "dom";
exports.VULNTYPE_REFLECTED = "reflected";
exports.VULNTYPE_STORED = "stored";
exports.VULNTYPE_WARNING = "warning";

139 changes: 82 additions & 57 deletions domdig.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
const consts = require("./consts");
const htcrawl = require('htcrawl');
const utils = require('./utils');
const defpayloads = require('./payloads').all;
Expand Down Expand Up @@ -42,7 +43,7 @@ async function scanAttributes(crawler){
} 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`);
utils.addVulnerability(VULNSJAR, consts.VULNTYPE_WARNING, PAYLOADMAP[key], null, `Attribute '${attr}' of '${es}' set to payload`, VERBOSE);
break;
}
}
Expand All @@ -60,50 +61,58 @@ async function triggerOnpaste(crawler){
}
}

async function loadCrawler(targetUrl, payload, options, trackUrlChanges){
async function loadCrawler(vulntype, targetUrl, payload, options, trackUrlChanges, vulnmess){
var hashSet = false;
var loaded = false;
var crawler;

do{
// instantiate htcrawl
crawler = await htcrawl.launch(targetUrl, options);

// set a sink on page scope
crawler.page().exposeFunction("___xssSink", function(key) {
var url = "";
if(crawler.page().url() != PREVURL){
url = PREVURL = crawler.page().url();
}
utils.addVulnerability(VULNSJAR, vulntype, PAYLOADMAP[key], trackUrlChanges ? url : null, vulnmess, VERBOSE);
});

// instantiate htcrawl
const crawler = await htcrawl.launch(targetUrl, options);

// set a sink on page scope
crawler.page().exposeFunction("___xssSink", function(key) {
var url = "";
if(crawler.page().url() != PREVURL){
url = PREVURL = crawler.page().url();
if(payload != null){
// fill all inputs with a payload
crawler.on("fillinput", async function(e, crawler){
const p = getNewPayload(payload, e.params.element);
try{
await crawler.page().$eval(e.params.element, (i, p) => i.value = p, p);
}catch(e){}
// return false to prevent element to be automatically filled with a random value
return false;
});


// change page hash before the triggering of the first event
crawler.on("triggerevent", async function(e, crawler){
if(!hashSet){
const p = getNewPayload(payload, "hash");
await crawler.page().evaluate(p => document.location.hash = p, p);
hashSet = true;
PREVURL = crawler.page().url();
}
});
}
utils.addVulnerability(PAYLOADMAP[key], VULNSJAR, trackUrlChanges ? url : null, VERBOSE);
});

if(payload != null){
// fill all inputs with a payload
crawler.on("fillinput", async function(e, crawler){
const p = getNewPayload(payload, e.params.element);
try{
await crawler.load();
loaded = true;
} catch(e){
utils.printError(`${e}`);
if(VERBOSE) utils.printInfo("Retrying . . .")
try{
await crawler.page().$eval(e.params.element, (i, p) => i.value = p, p);
}catch(e){}
// return false to prevent element to be automatically filled with a random value
return false;
});


// change page hash before the triggering of the first event
crawler.on("triggerevent", async function(e, crawler){
if(!hashSet){
const p = getNewPayload(payload, "hash");
await crawler.page().evaluate(p => document.location.hash = p, p);
hashSet = true;
PREVURL = crawler.page().url();
}
});
}

try{
await crawler.load();
} catch(e){
console.log(`Error ${e}`);
process.exit(-3);
}
await crawler.browser().close();
} catch(e1){}
}
} while(!loaded);

PREVURL = crawler.page().url();

Expand Down Expand Up @@ -169,6 +178,21 @@ async function close(crawler){
crawler.browser().close();
}

async function scanStored(url, options){
ps("Scanning DOM for stored XSS");
const crawler = await loadCrawler(consts.VULNTYPE_STORED, url, null, options, true);
// disable post request since they can overwrite injected payloads
const cancelPostReq = function(e){return e.params.request.method == "GET"};

crawler.on("xhr", cancelPostReq);
crawler.on("fetch", cancelPostReq);
await scanDom(crawler, options);
await triggerOnpaste(crawler);
await scanAttributes(crawler);
await close(crawler);
ps("Stored XSS scan finshed");
}

function ps(message){
if(VERBOSE)utils.printStatus(message);
}
Expand Down Expand Up @@ -196,32 +220,41 @@ function ps(message){
}

const options = utils.parseArgs(argv, targetUrl);
const checks = argv.C ? argv.C.split(",") : ['dom', 'reflected', 'stored'];
options.crawlmode = "random"
const checks = argv.C ? argv.C.split(",") : [consts.CHECKTYPE_DOM, consts.CHECKTYPE_REFLECTED, consts.CHECKTYPE_STORED];
var payloads = argv.P ? utils.loadPayloadsFromFile(argv.P) : defpayloads;

if(checks.length == 1 && checks[0] == consts.CHECKTYPE_STORED){
if(VERBOSE)utils.printWarning("Cannot check for stored without dom or reflected scan. Forcing dom scan.");
checks.push("dom");
}
ps("Starting scan");

if(checks.indexOf("dom") != -1){
if(checks.indexOf(consts.CHECKTYPE_DOM) != -1){
cnt = 1;
for(let payload of payloads){
ps("Scanning DOM");
crawler = await loadCrawler(targetUrl.href, payload, options, true);
crawler = await loadCrawler(consts.VULNTYPE_DOM, targetUrl.href, payload, options, true);
await scanDom(crawler, options);
await triggerOnpaste(crawler);
await scanAttributes(crawler);
await close(crawler);
if(checks.indexOf(consts.CHECKTYPE_STORED) != -1){
await scanStored(targetUrl.href, options);
}
ps(cnt + "/" + payloads.length + " payloads checked");
cnt++;
}
}

// check for reflected XSS
if(checks.indexOf("reflected") != -1){
if(checks.indexOf(consts.CHECKTYPE_REFLECTED) != -1){
cnt = 1;
for(let payload of payloads){
ps("Checking reflected");
for(let mutUrl of getUrlMutations(targetUrl, payload)){
let totv = VULNSJAR.length;
crawler = await loadCrawler(mutUrl.href, payload, options);
crawler = await loadCrawler(consts.VULNTYPE_REFLECTED, mutUrl.href, payload, options);
if(totv != VULNSJAR.length) {
//ps("Vulnerability found, skipping DOM scan");
} else {
Expand All @@ -230,31 +263,23 @@ function ps(message){
await triggerOnpaste(crawler);
await scanAttributes(crawler);
await close(crawler);
if(checks.indexOf(consts.CHECKTYPE_STORED) != -1){
await scanStored(targetUrl.href, options);
}
ps(cnt + "/" + payloads.length + " payloads checked");
}
cnt++;
}
}

// check for stored XSS
if(checks.indexOf("stored") != -1){
ps("Scanning DOM for stored XSS");
crawler = await loadCrawler(targetUrl.href, null, options);
await scanDom(crawler, options);
await triggerOnpaste(crawler);
await scanAttributes(crawler);
await close(crawler);
ps("Store XSS scan finshed");
}

if(VERBOSE)console.log("");
ps("Scan finished, tot vulnerabilities: " + VULNSJAR.length);

if(argv.J){
console.log(utils.prettifyJson(VULNSJAR));
} else if(VERBOSE){
for(let v of VULNSJAR){
utils.printVulnerability(v[0], v[1], v[2], v[3]);
utils.printVulnerability(v);
}
}

Expand Down
56 changes: 46 additions & 10 deletions utils.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
const consts = require("./consts");
const fs = require('fs');
const chalk = require('chalk');

Expand All @@ -8,32 +9,56 @@ exports.error = error;
exports.addVulnerability = addVulnerability;
exports.printVulnerability = printVulnerability;
exports.printStatus = printStatus;
exports.printInfo = printInfo;
exports.printWarning = printWarning;
exports.printError = printError;
exports.sequenceError = sequenceError;
exports.writeJSON = writeJSON;
exports.prettifyJson = prettifyJson;
exports.loadPayloadsFromFile = loadPayloadsFromFile;
exports.error = error;
exports.getElementSelector = getElementSelector;
exports.Vulnerability = Vulnerability;

function addVulnerability(vuln, jar, url, verbose, message){
function Vulnerability(type, payload, element, url, message){
this.type = type;
this.payload = payload;
this.element = element || "N/A";
this.url = url || "";
if(message){
this.message = message;
} else switch (type){
case consts.VULNTYPE_DOM:
this.message = "DOM XSS found";
break;
case consts.VULNTYPE_REFLECTED:
this.message = "Reflected DOM XSS found";
break;
case consts.VULNTYPE_STORED:
this.message = "Stored XSS found";
break;
}
}

function addVulnerability(jar, type, vuln, url, message, verbose){
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)){
if(e.payload == p && e.element == vuln.element && e.message == message && (!url || e.url == url)){
return;
}
}
jar.push([p, vuln.element, url, message]);
const v = new Vulnerability(type, p, vuln.element, url, message);
jar.push(v);
if(verbose){
printVulnerability(p, vuln.element, url, message);
printVulnerability(v);
}
}

function printVulnerability(payload, element, url, message){
message = message || "DOM XSS found";
var msg = chalk.red('[!]') + ` ${message}: ${element}${payload}`;
if(url){
msg += " → " + url;
function printVulnerability(v){
var msg = chalk.red('[!]') + ` ${v.message}: ${v.element}${v.payload}`;
if(v.url){
msg += " → " + v.url;
}
console.log(msg);
}
Expand All @@ -42,6 +67,17 @@ function printVulnerability(payload, element, url, message){
function printStatus(mess){
console.log(chalk.green("[*] ") + mess);
}
function printInfo(mess){
console.log(chalk.blue("[*] ") + mess);
}

function printWarning(mess){
console.log(chalk.yellow("[!] ") + mess);
}

function printError(mess){
console.log(chalk.red("[!] ") + mess);
}

function error(message){
console.error(chalk.red(message));
Expand Down Expand Up @@ -85,7 +121,7 @@ function usage(){
" -J print findings as JSON",
" -q quiet mode",
" -P PATH load payloads from file (JSON)",
" -C CHECKS comma-separated list of checks: dom,reflected (default: all)",
" -C CHECKS comma-separated list of checks: dom,reflected,stored (default: all)",
" -h this help"
].join("\n"));
}
Expand Down

0 comments on commit 11e43ca

Please sign in to comment.