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