diff --git a/README.md b/README.md index 679c988..074b060 100644 --- a/README.md +++ b/README.md @@ -70,7 +70,7 @@ Don't worry, just modify your wordlist to use `puff()` instead of `alert()` in -Running from source: +**Running from source:** ``` node puff.js -w xss.txt -u "http://your.url?message=FUZZ" @@ -79,7 +79,7 @@ node puff.js -w xss.txt -u "http://your.url?message=FUZZ" -t 25 node puff.js -w xss.txt -u "http://your.url?message=FUZZ" -d ``` -installed via npm: +**installed via npm:** ``` puff -w xss.txt -u "http://your.url?message=FUZZ" @@ -88,6 +88,42 @@ puff -w xss.txt -u "http://your.url?message=FUZZ" -t 25 puff -w xss.txt -u "http://your.url?message=FUZZ" -d ``` + + +**Running with stdin fuzzing mode:** +``` +cat urls.txt | node puff.js -w .\wordlist-examples\events.txt + + +cat urls.txt | puff -w .\wordlist-examples\events.txt +``` +Where urls.txt is +``` +http://example.com?query=FUZZ +https://another.com/page/#FUZZ +``` + + +**Running with stdin single payload mode:** +``` +cat urls.txt | node puff.js -w .\wordlist-examples\events.txt + + +cat urls.txt | puff -w .\wordlist-examples\events.txt +``` + +Where urls.txt is +``` +http://example.com?query= +http://example.com?query=javascript:alert() +https://another.com/page/# +``` + + + + + + ---

diff --git a/callbacks.js b/callbacks.js index 8a2ef83..d47d4a7 100644 --- a/callbacks.js +++ b/callbacks.js @@ -47,11 +47,11 @@ class TriggerHandler{ }) //xss windows tend to get load looped, but not sure if needed - //thread.evaluate(() => window.stop()); + thread.evaluate(() => window.stop()); } catchLoadFailure(thread){ - this.outputHandler.write(`${bstart}[${thread.status}] [FAILURE] ${thread.pld} ${colstop}`, 5000) + this.outputHandler.write(`${bstart} [${thread.status}] [FAILURE] ${thread.pld} ${colstop}`, 5000) this.outputHandler.bLastOutputImportant=true } diff --git a/fuzzers/SinglePayloadFuzzer.js b/fuzzers/SinglePayloadFuzzer.js new file mode 100644 index 0000000..ee6197a --- /dev/null +++ b/fuzzers/SinglePayloadFuzzer.js @@ -0,0 +1,75 @@ + +const {fail,succ,warn,info,gstart,bstart,ystart,rstart,colstop} = require('../pretty.js') +const fs = require('fs') +function replaceKeyword(url,pld){ + pld = pld.replace(/ /g, '%20') + var t=url; + t = t.replace(/FUZZ/g,pld.replace(/\n|\r/g,'')) + return t; + +} + + +class SinglePayloadFuzzer{ + /* + * Fuzzer for when both wordlist and url parameter is supplied. + * Read url from the parameter, and read + * + */ + constructor(url,cbhandler, threadHandler, terminator, wordlist, verbose, multi=false){ + this.url = url + this.wlistFpointer=0; + this.cbHandler=cbhandler + this.terminator = terminator + this.verbose = verbose + this.threadHandler = threadHandler; + this.wordlist = wordlist; + this.multi = multi; + + } + + + async loadNextUrl(thread){ + /* + * Load next url from from the wordlist + */ + thread.url = this.url + thread.pld = '' + await this.processURL(thread,thread.url) + thread.close() + } + + + async processURL(thread, url){ + /* + * Process url, visit, try to trigger events etc. + */ + try{ + thread.goto(thread.url) + + //capture window response + const response = await thread.waitForNavigation(); + + //acquire possible redirect chain + var chain = (response.request().redirectChain()) + + //get http response + thread.status = response.status(); + + //if there was a redirect chain, output it. If not, its a normal response + if(chain.length){ + thread.wasHTTPRedirect = true; + this.cbHandler.catchRedirect(thread, chain) + }else{ + this.cbHandler.catchNormal(thread) + } + }catch(e){ + //Not properly implemented yet, dom-errors, http timeouts + this.cbHandler.catchLoadFailure(thread) + } + } +} + +module.exports = { + SinglePayloadFuzzer:SinglePayloadFuzzer +} \ No newline at end of file diff --git a/SingleUrlFuzzer.js b/fuzzers/SingleUrlFuzzer.js similarity index 84% rename from SingleUrlFuzzer.js rename to fuzzers/SingleUrlFuzzer.js index 8bbcf6c..6ec51c4 100644 --- a/SingleUrlFuzzer.js +++ b/fuzzers/SingleUrlFuzzer.js @@ -1,5 +1,5 @@ -const {fail,succ,warn,info,gstart,bstart,ystart,rstart,colstop} = require('./pretty.js') +const {fail,succ,warn,info,gstart,bstart,ystart,rstart,colstop} = require('../pretty.js') const fs = require('fs') function replaceKeyword(url,pld){ pld = pld.replace(/ /g, '%20') @@ -16,7 +16,7 @@ class SingleUrlFuzzer{ * Read url from the parameter, and read * */ - constructor(url,cbhandler, threadHandler, terminator, wordlist, verbose){ + constructor(url,cbhandler, threadHandler, terminator, wordlist, verbose, multi=false){ this.url = url this.wlistFpointer=0; this.cbHandler=cbhandler @@ -24,6 +24,7 @@ class SingleUrlFuzzer{ this.verbose = verbose this.threadHandler = threadHandler; this.wordlist = wordlist; + this.multi = multi; //read wordlist if(this.verbose) console.log(`${warn} Reading Wordlist`) @@ -42,11 +43,13 @@ class SingleUrlFuzzer{ /* * Acquire next url from wordlist */ + + var line = this.wlistContent[this.wlistFpointer]; this.wlistFpointer+=1 - return this.wlistContent[this.wlistFpointer]; + return line } - async checkFinished(){ + checkFinished(){ /* * Check if wordlist finished */ @@ -59,25 +62,36 @@ class SingleUrlFuzzer{ outputHandler.deleteLastLine() console.log("Thread finished") } - + this.threadHandler.workerCount-=1; //Only terminate program if all the threads have finished, so it doesn't lose the progress on those pending requests. - if(this.terminator.terminatedCount==threadHandler.workerCount){ + if(this.threadHandler.workerCount==0){ //TODO, timeout possible idle/stuck threads and terminate if(this.verbose){ outputHandler.deleteLastLine() outputHandler.write('Last url checked, waiting for all threads to finish') } - this.terminator.terminate() + if(this.multi){ + console.log('') + }else{ + this.terminator.terminate() + } } + return true; } + return false; } async loadNextUrl(thread){ /* * Load next url from from the wordlist */ - this.checkFinished() + if(this.checkFinished()){ + try{ + await thread.close() + }catch(e){}; + return; + } var line = await this.acquire() thread.url = await replaceKeyword(this.url, line) thread.pld = line diff --git a/fuzzers/StdinFuzzer.js b/fuzzers/StdinFuzzer.js new file mode 100644 index 0000000..3f9520d --- /dev/null +++ b/fuzzers/StdinFuzzer.js @@ -0,0 +1,137 @@ + +const {fail,succ,warn,info,gstart,bstart,ystart,rstart,colstop} = require('../pretty.js') + +function replaceKeyword(url,pld){ + pld = pld.replace(/ /g, '%20') + var t=url; + t = t.replace(/FUZZ/g,pld.replace(/\n|\r/g,'')) + return t; + +} + + +class SingleUrlFuzzer{ + /* + * Fuzzer for when both wordlist and url parameter is supplied. + * Read url from the parameter, and read + * + */ + constructor(url,cbhandler, threadHandler, terminator, wordlist, verbose, multi=false){ + this.url = url + this.wlistFpointer=0; + this.cbHandler=cbhandler + this.terminator = terminator + this.verbose = verbose + this.threadHandler = threadHandler; + this.wordlist = wordlist; + this.multi = multi; + + //read wordlist + if(this.verbose) console.log(`${warn} Reading Wordlist`) + try{ + this.wlistContent = fs.readFileSync(wordlist).toString().split("\n") + }catch(e){ + console.log(`${fail} Wordlist file was not found`) + console.log(e) + process.exit(1) + } + if(this.verbose) console.log(`${succ} Wordlist loaded, ${this.wlistContent.length} lines.`) + + } + + async acquire(){ + /* + * Acquire next url from stdin + */ + + var line = this.wlistContent[this.wlistFpointer]; + this.wlistFpointer+=1 + return line + } + + checkFinished(){ + /* + * Check if wordlist finished + */ + + //if this thread is done + if(this.wlistFpointer>=this.wlistContent.length){ + this.terminator.terminatedCount+=1 + this.wlistFpointer+=1 + if(this.verbose){ + outputHandler.deleteLastLine() + console.log("Thread finished") + } + this.threadHandler.workerCount-=1; + //Only terminate program if all the threads have finished, so it doesn't lose the progress on those pending requests. + if(this.threadHandler.workerCount==0){ + //TODO, timeout possible idle/stuck threads and terminate + if(this.verbose){ + outputHandler.deleteLastLine() + outputHandler.write('Last url checked, waiting for all threads to finish') + } + + if(this.multi){ + console.log('') + }else{ + this.terminator.terminate() + } + } + return true; + } + return false; + } + + async loadNextUrl(thread){ + /* + * Load next url from from the wordlist + */ + if(this.checkFinished()){ + try{ + await thread.close() + }catch(e){}; + return; + } + var line = await this.acquire() + thread.url = await replaceKeyword(this.url, line) + thread.pld = line + this.processURL(thread,thread.url) + } + + + async processURL(thread, url){ + /* + * Process url, visit, try to trigger events etc. + */ + try{ + thread.goto(thread.url) + + //capture window response + const response = await thread.waitForNavigation(); + + //acquire possible redirect chain + var chain = (response.request().redirectChain()) + + //get http response + thread.status = response.status(); + + //if there was a redirect chain, output it. If not, its a normal response + if(chain.length){ + thread.wasHTTPRedirect = true; + this.cbHandler.catchRedirect(thread, chain) + }else{ + this.cbHandler.catchNormal(thread) + } + }catch(e){ + //Not properly implemented yet, dom-errors, http timeouts + this.cbHandler.catchLoadFailure(thread) + } + + //recurses + this.loadNextUrl(thread) + } +} + +module.exports = { + SingleUrlFuzzer:SingleUrlFuzzer +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 20f3dbf..ae6b961 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "puff-fuzz", - "version": "0.0.6", + "version": "0.1.1", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -263,6 +263,11 @@ "util-deprecate": "^1.0.1" } }, + "readline-sync": { + "version": "1.4.10", + "resolved": "https://registry.npmjs.org/readline-sync/-/readline-sync-1.4.10.tgz", + "integrity": "sha512-gNva8/6UAe8QYepIQH/jQ2qn91Qj0B9sYjMBBs3QOB8F2CXcKgLxQaJRP76sWVRQt+QU+8fAkCbCvjjMFu7Ycw==" + }, "rimraf": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", diff --git a/package.json b/package.json index d276da7..379cb1f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "puff-fuzz", - "version": "0.1.0", + "version": "0.1.1", "description": "Simple Clientside vulnerability/xss fuzzer", "main": "puff.js", "repository": "https://github.com/FlameOfIgnis/puff", diff --git a/puff.js b/puff.js index aa8f407..5127688 100644 --- a/puff.js +++ b/puff.js @@ -6,7 +6,7 @@ const puppeteer = require('puppeteer'); const { program } = require('commander'); const fs = require('fs') const path = require('path') - +const readline = require('readline') //config wizard and io handlers const {setChromePath,resolveChromiumPath} = require('./configwiz.js') @@ -60,13 +60,6 @@ var config = require(path.join(__dirname,'/config.json')) //resolve ch var chromium_path = resolveChromiumPath(config); -//check if required parameters were given -if(!(program.wordlist || program.url)){ - console.log('Wordlist and url are required parameters.') - process.exit() -} - - //init tool (async () => { @@ -80,16 +73,75 @@ if(!(program.wordlist || program.url)){ terminator.browser = browser; threadHandler = new ThreadHandler(browser) - - const {SingleUrlFuzzer} = require('./SingleUrlFuzzer.js') - var suFuzzer = new SingleUrlFuzzer(program.url, cbHandler, threadHandler, terminator, wordlist, verbose); + + //normal mode + if(program.wordlist && program.url){ + const {SingleUrlFuzzer} = require('./fuzzers/SingleUrlFuzzer.js') + var suFuzzer = new SingleUrlFuzzer(program.url, cbHandler, threadHandler, terminator, wordlist, verbose); + //initialize threads + for(var i=0;i { + if(url=='') return + var suFuzzer = new SingleUrlFuzzer(url, cbHandler, threadHandler, terminator, wordlist, verbose, true); + //initialize threads + for(var i=0;i{ + terminator.graceful() + }) + + + //get payloads from stdin + }else if(program.url){ + console.log('Invalid arguements.')//This mode is not yet implemented + process.exit(1) - //initialize threads - for(var i=0;i { + if(url=='') return + var singlePayloadFuzzer = new SinglePayloadFuzzer(url, cbHandler, threadHandler, terminator, verbose); + var newThread = threadHandler.newThread(browser, singlePayloadFuzzer, cbHandler); + }); + + rl.on('SIGINT', ()=>{ + terminator.graceful() + }) + } -})(); \ No newline at end of file + + + +})();