Last active
April 30, 2017 16:49
-
-
Save thomashartm/97dc9865b531f1d682842aa20ba621a6 to your computer and use it in GitHub Desktop.
Jinx 2.0 is a slightly adapted version of Martin Holst Swende's great GreaseMonkey script Jinx. Please see the visit the original code http://swende.se/projects/jinx.html
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// ==UserScript== | |
// @name Jinx 2.0 | |
// @namespace swende.se | |
// @grant GM_registerMenuCommand | |
// @description This is a slightly adapted version of Martin Holst Swende's great GM script Jinx. Please see the original code http://swende.se/projects/jinx.html | |
// @version 1 | |
// @include * | |
// @require http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js | |
// ==/UserScript== | |
console.log("Jinx 2.0 loaded for " + window.document.documentURI) | |
var jx_activeDoc = null; | |
var abort = false; | |
var separator = "$" | |
var supportsSelectors = false; | |
/****************** | |
* Debugging / Console - methods | |
*******************/ | |
function dbg(vars) | |
{ | |
console.log(vars); | |
} | |
function log(vars) | |
{ | |
print(vars,"grey"); | |
} | |
function report(vars) | |
{ | |
print(vars,"black"); | |
} | |
function scream(vars,href) | |
{ | |
printResult(vars,"black",href); | |
} | |
prints = []; | |
function flush() | |
{ | |
if(prints.length == 0) return; | |
var t = getConsole(); | |
var content = t.innerHTML; | |
for(elem in prints) | |
{ | |
[text,color] = prints[elem]; | |
if(color != null)content = content+"<span style='color:"+color+"'>"+esc(text)+"</span><br/>"; | |
else content = content+esc(text)+"<br/>"; | |
} | |
t.innerHTML = content; | |
prints = []; | |
} | |
function print(text, color) | |
{ | |
prints.push([text,color]) | |
} | |
function printResult(text, color,href) | |
{ | |
var t = getReportOutputConsole(); | |
if(color != null)t.innerHTML = t.innerHTML+"<span style='color:"+color+"'><a style='color:"+color+";font-weight:Normal;' href=\""+href.replace("\"","%22")+"\">"+esc(text)+"</a></span><br/>"; | |
else t.innerHTML = t.innerHTML+esc(text)+"<br/>"; | |
} | |
function esc (str) | |
{ | |
var div = document.createElement('div'); | |
var text = document.createTextNode(str); | |
div.appendChild(text); | |
return div.innerHTML; | |
}; | |
function closeConsoles(){ | |
var t = getConsole(); | |
console.log(t); | |
if(t && t.parentElement){ | |
t.parentElement.remove(); | |
console.log("Removed console"); | |
} | |
var r = getReportOutputConsole(); | |
if(r && r.parentElement){ | |
r.parentElement.remove(); | |
console.log("Removed reported console"); | |
} | |
}; | |
/******************** | |
* Javascript displayer, an output window for general information | |
/********************/ | |
var cscheme = ["#009999","#006666","#CCFFFF","#99FFFF"] | |
function getConsole() | |
{ | |
var id ='console4565asdfalsdfk54ad2341231123fasdf654'; | |
return getAConsole(id,"#E5E4D9","10%","5%","40%","80%"); | |
} | |
/** | |
*Another output window, for results | |
**/ | |
function getReportOutputConsole() | |
{ | |
var id ='console4565asdfalsdfasdfWEFASDFWQAEFSDFASDF54'; | |
return getAConsole(id,"#EEDDFF","50%","5%","40%","80%"); | |
} | |
function getAConsole(id,bgcol,left,top,width,height) | |
{ | |
var jx_doc = getActiveDocument(); | |
var t = jx_doc.getElementById(id); | |
if(t) | |
{ | |
return t; | |
} | |
//Create it | |
var jx_div = jx_doc.createElement("div"); | |
//Set some styles | |
jx_div.style.textAlign = "left" | |
jx_div.style.backgroundColor = bgcol; | |
jx_div.style.position ="fixed"; | |
jx_div.style.left =left; | |
jx_div.style.top =top; | |
jx_div.style.width =width; | |
jx_div.style.height =height; | |
jx_div.style.zIndex = 10000002; | |
jx_div.style.overflow = "scroll"; | |
jx_div.style.border = "1px solid black"; | |
//Add it | |
jx_doc.body.appendChild(jx_div); | |
jx_div.innerHTML ="<span id='"+id+"' style='float: left;width: 100%; height: 80%; font-family: Tahoma ;font-size:8pt;text-align:left;'></span>"; | |
return jx_doc.getElementById(id); | |
} | |
/** | |
Executes some checks to find out more about the actual page. | |
**/ | |
function analyze(){ | |
} | |
/** | |
Returns the active document object. For non-frame sites, that will be the 'normal' document object, otherwise | |
one of the frame documents will be returned | |
**/ | |
function getActiveDocument() | |
{ | |
if(jx_activeDoc != null) return jx_activeDoc; | |
//Handle frames-problem | |
if(window.frames.length > 1) | |
{ | |
var ok = new Array(); | |
var nok = new Array(); | |
for(var i = 0; i < window.frames.length; i++) | |
{ | |
try{ | |
var fsrc = window.frames[i].src; | |
var fsrc = window.frames[i].location; | |
ok.push([i,fsrc]); | |
}catch (e) | |
{ | |
nok.push([i,e]); | |
} | |
} | |
var txt = "This page contains "+window.frames.length+" frames. "+ok.length+" can be accessed:\r\n"; | |
for(var i in ok) | |
{ | |
txt += ok[i][0]+" : "+ok[i][1]+"\r\n"; | |
} | |
txt += nok.length +" cannot be accessed:\r\n"; | |
for(var i in nok) | |
{ | |
txt += nok[i][0]+" : "+nok[i][1]+"\r\n"; | |
} | |
txt +="\r\nThere is also the current location. Which one do you want to operate on ?\r\n(Enter for current location, or choose frame number)"; | |
while(jx_activeDoc == null) | |
{ | |
var v = prompt(txt); | |
if(v == null) return null; | |
if(v =="") jx_activeDoc = document; | |
else | |
{ | |
v = Number(v); | |
if(isNaN(v)) continue; | |
if(v < 0 || v > window.frames.length) continue; | |
jx_activeDoc = window.frames[v].document; | |
} | |
} | |
}else | |
{ | |
jx_activeDoc = document; | |
} | |
return jx_activeDoc; | |
} | |
function setActiveFrame(activeFrame) | |
{ | |
if(isNumeric(activeFrame) && window.frames.length > activeFrame) | |
{ | |
var jx_activeFrame = activeFrame; | |
} | |
} | |
var ANALYSE_V= [ | |
{"desc" : "Get title of page" | |
,"test" : function(content){ | |
var msg; | |
var re = /<title>\s+(.*)<\/title>/mi; | |
var m = re.exec(content); | |
if (m == null) { | |
var i1 = content.indexOf("<title>")+7; | |
var i2 = content.indexOf("</title>"); | |
//alert(i1+""+i2); | |
msg = content.substring(i1,i2); | |
//msg =" - no title -"; | |
//alert(content); | |
} else { | |
var s = "Match at position " + m.index + ":\n"; | |
msg = m[1]; | |
} | |
return {"hit" : true, "message" : msg}; | |
} | |
} | |
] | |
/** Attack vectors **/ | |
var COMPLETE_V = [ | |
{"desc" : "No double-quote filter" | |
,"sig" : "gza\"azg" | |
,"test" : function(content){ | |
return {"hit" : (content.indexOf(this.sig) > -1), "message" : this.desc}; | |
} | |
} | |
,{"desc" : "Single-quote unfiltered or SQL injection" | |
,"sig" : "gzb'bzg" | |
,"test" : function(content) | |
{ | |
return {"hit" : ((content.indexOf(this.sig) > -1) || (content.indexOf("SQL") > -1)), "message" : this.desc}; | |
} | |
} | |
,{"desc" : "No filter on less than" | |
,"sig" : "gzc<czg" | |
,"test" : function(content) | |
{ | |
return {"hit" : (content.indexOf(this.sig) > -1), "message" : this.desc}; | |
} | |
} | |
,{"desc" : "Decodes encoded data" | |
,"sig" : "gze%3Cezg" | |
,"test" : function(content) | |
{ | |
return {"hit" : (content.indexOf("gze<ezg") > -1), "message": this.desc}; | |
} | |
} | |
,{"desc" : "Decodes double-encoded data" | |
,"sig" : "gzf%253Cfzg" | |
,"test" : function(content) | |
{ | |
return {"hit": (content.indexOf("gze<ezg") > -1), "message": this.desc}; | |
} | |
} | |
,{"desc" : "Decodes overlong UTF-8 data" | |
,"sig" : "gzh%c0%bchzg" | |
,"test" : function(content) | |
{ | |
return {"hit":(content.indexOf("gzh<hzg") > -1), "message": this.desc}; | |
} | |
} | |
]; | |
/** | |
Quick and dirty - vector, injects three XSS-prone chars at once: '"< | |
**/ | |
var QUICK_V = [ | |
{"desc" : "Usual suspects" | |
,"sig" : "hzg<izg\"jzg'kzg%c0%bcgzh"// Four checks in one | |
,"test" : function(content) | |
{ | |
var message ="Unfiltered chars: "; | |
var hit = false; | |
if(content.indexOf("hzg<izg") > -1) | |
{ | |
hit = true; | |
message += " < "; | |
} | |
if(content.indexOf("izg\"jzg") > -1) | |
{ | |
hit = true; | |
message += " \" "; | |
} | |
if(content.indexOf("jzg'kzg") > -1) | |
{ | |
hit = true; | |
message += " ' "; | |
} | |
if(content.indexOf("kzg<gzh") > -1) | |
{ | |
hit = true; | |
message += " OverlongUTF8 < "; | |
} | |
return {"hit" : hit, "message" : message}; | |
} | |
} | |
// {"desc" : "XSS Locator 2" | |
// ,"sig" : "'';!--\"<XSS>=&{()}" | |
// ,"test" : function(content){return (content.indexOf(this.sig) > -1)} | |
// } | |
]; | |
var _current = QUICK_V; | |
function getAttackVectors() | |
{ | |
return _current; | |
} | |
/** | |
* Finds all unique links in document. Returns the ones that are within domain | |
**/ | |
function locateLinks() | |
{ | |
var jx_doc = getActiveDocument(); | |
var links = jx_doc.getElementsByTagName("a"); | |
var arr = new Array(); | |
var list = new Array(); | |
var loc = jx_doc.location; | |
if(loc== null) | |
{ | |
alert(jx_doc.location+" is null. "); | |
alert(jx_doc) | |
} | |
arr[loc] = loc; | |
list.push(loc); | |
alert("Using loc : "+loc); | |
var mydom = getDomain(loc); | |
var skiplog = ""; | |
for(var i = 0 ; i < links.length ; i++) { | |
var link = links[i].href; | |
//log(link); | |
var linkdom = getDomain(link); | |
if(linkdom == mydom) | |
{ | |
if(arr[link] == null) list.push(link); | |
arr[link] = 1; | |
dbg("Added : "+link); | |
} | |
else | |
dbg("Skipping : "+link); | |
} | |
return list; | |
} | |
/** | |
Gets domain of an URI | |
*/ | |
function getDomain(URI) | |
{ | |
var uriObj = parseURI(URI); | |
if(uriObj == null) return null; | |
return uriObj["domain"]; | |
} | |
/** | |
Gets domain of an URI | |
*/ | |
function getPath(URI) | |
{ | |
var uriObj = parseURI(URI); | |
if(uriObj == null) return null; | |
return uriObj["path"]; | |
} | |
/** | |
Splits a URI | |
**/ | |
function parseURI(URI) | |
{ | |
var object; | |
var uriRegexp = new RegExp("^(https?://)+([^/\\&\?]*)([^\?&]*)(.*)$","g"); | |
try | |
{ | |
var split = uriRegexp.exec(URI); | |
object = {"method" : split[1] , "domain" : split[2]}; | |
if(split.length > 2) object["path"] = split[3]; | |
if(split.length > 3) object["query"] = split[4]; | |
}catch(e){ | |
if(URI.length > 8 && "mailto:"==URI.slice(0,7)) log("Found email:"+URI); | |
else log("Skipping non-http uri:"+URI); | |
} | |
return object; | |
} | |
/** | |
Returns parameters of an already parsed URI object | |
**/ | |
function getParameters(URIObject) | |
{ | |
var params = URIObject["params"]; | |
if(params != null) return params; | |
var params = new Array(); | |
URIObject["params"] = params; | |
var q = URIObject["query"]; | |
if(q == null || q.length == 0) return params; | |
if(q.charAt(0) == "?") q=q.slice(1); | |
var keyvalues = q.split("&"); | |
for each(keyvalue in keyvalues) | |
{ | |
var key = keyvalue.split("=")[0]; | |
var val = keyvalue.split("=")[1]; | |
params[key] = val ? val : ""; | |
} | |
return params; | |
} | |
/** | |
Permutates urls. Returns an array containing permutated links | |
**/ | |
function permutate(list) | |
{ | |
var all = new Array(); | |
var allAttackVectors = getAttackVectors(); | |
for(var i =0 ; i < list.length; i++) | |
{ | |
var uri = list[i]; | |
for(var j = 0 ; j < allAttackVectors.length ; j++) | |
{ | |
var attack = allAttackVectors[j]; | |
var permutations = applyAttackVector(uri,attack, false); | |
for(var k = 0 ; k < permutations.length;k++) | |
{ | |
var permutation = permutations[k]; | |
if(permutation != null){ | |
//log("About to push "+permutation); | |
all.push({"url" : permutation,"vector" : attack}); | |
} | |
} | |
} | |
} | |
return all; | |
} | |
/** | |
Applies an attackvector to a URI | |
What it currently does is | |
for each URI | |
for each parameter | |
append attack payload to parameter | |
remove duplicates | |
**/ | |
function applyAttackVector(URI,attack, detailed) | |
{ | |
// log("applyAttackVector("+URI+","+attack+")"); | |
// example.com/path/ ?foo=bar&apa=monkey gazonk | |
// ==> Level 1 (three requests, no matter how many parameters | |
//0 example.com/path/?foo=bar&apa=monkey&gazonk | |
//1 example.com/path/?foo=gazonk&apa=gazonk | |
//2 example.com/path/?foo=bargazonk&apa=monkeygazonk | |
// | |
// Level 2 (adds one request per parameter), if detailed is set | |
// | |
// example.com/path/?foo=bargazonk&apa=monkey | |
// example.com/path/?foo=bar&apa=monkeygazonk | |
var appendstyleVectors = new Array(); | |
var uriObj = parseURI(URI); | |
if(uriObj == null) return appendstyleVectors;//Bad URI | |
var params = getParameters(uriObj); | |
for(key in params) | |
{ | |
appendstyleVectors.push(toUrl(uriObj,attack["sig"],"append", key)); | |
} | |
return removeDuplicates(appendstyleVectors); | |
} | |
/** | |
* Creates a fuzzed link. | |
* @uriObject - a parsed uriObject (array) | |
* @payload - the payload (injection) to apply | |
* @type - Either "replace" or append". Replace will | |
* replace a value with the payload, append will append | |
* the payload to the value. | |
* @which - which parameter (key) to operate on. If omitted, it is done to every parameter | |
* @tail - if true, will apply the payload as a parameter at the end of the url | |
**/ | |
function toUrl(uriObject, payload, type, which,tail) | |
{ | |
var query = "?"; | |
var params = getParameters(uriObject); | |
for(key in params) | |
{ | |
if(query.length > 1) | |
query +="&"; | |
var value = params[key]; | |
query += key + "="; | |
if(which == null || which == key) | |
{ | |
if("append" == type) | |
{ | |
query += value; | |
} | |
query += payload; | |
}else | |
{ | |
//Build the query as usual | |
query += value; | |
} | |
} | |
if(tail) | |
{//Append to tail of quer | |
if(query.length > 1) | |
query +="&"; | |
query +=payload; | |
} | |
var retval; | |
if(query.length > 1) | |
{ | |
retval = uriObject["method"]+uriObject["domain"]+uriObject["path"]+query; | |
} | |
//log("Tourl returning: "+retval); | |
return retval; | |
} | |
function removeDuplicates(list) | |
{ | |
var newlist = new Array(); | |
var array =new Array(); | |
for(var i = 0 ; i < list.length ; i++) | |
{ | |
var key = list[i]; | |
//key=key.replace(".*(?)$",""); | |
if(array[key] == null) | |
{ | |
newlist.push(key); | |
} | |
array[key] = key; | |
} | |
return newlist; | |
} | |
/** | |
Debugging stuff | |
**/ | |
var printArray = function (x, idx) { | |
log("["+idx+"] = "+x.toString()); | |
} | |
var showArray = function (x, idx) { | |
report("["+idx+"] = "+x.url); | |
} | |
function genUrlsBruteForce(start, end,base) | |
{ | |
var urls = new Array(); | |
for(var i =start ; i < end ; i++) | |
{ | |
var url = base + i; | |
urls.push({"url":url, "vector" : ANALYSE_V[0]}); | |
} | |
return urls; | |
} | |
function bruteforce() | |
{ | |
log("Starting tests"); | |
flush(); | |
//return; | |
// var urls = genUrlsBruteForce(96230,96260,"http://../c/"); | |
// var urls = genUrlsBruteForce(1740450,1740452,"http://../v/"); | |
// var urls = genUrlsBruteForce(118540,118600,"http://../t/"); | |
var urls = genUrlsBruteForce(1,3,"http://swende.se/t/"); | |
fetchSequential(urls,0); | |
flush(); | |
} | |
function failedUrl(request) | |
{ | |
var url = request.activeurl; | |
var batch = request.batch; | |
report(request.index+separator + request.status+ separator +request.activeurl); | |
flush(); | |
fetchSequentialUrl(batch,request.index); | |
} | |
/** | |
Enumerate | |
**/ | |
function enumerate(){ | |
console.log("Enumerate"); | |
var jx_doc = getActiveDocument(); | |
var links = jx_doc.getElementsByTagName("link"); | |
var done = false; | |
for(var i = 0 ; i < links.length && !done; i++) { | |
var link = links[i].href; | |
console.log(link); | |
//log(link); | |
var linkPath = getPath(link); | |
if(linkPath.indexOf("/etc/design") > -1){ | |
log("Detected Adobe AEM. Selector or suffix based XSS might be possible"); | |
supportsSelectors = true; | |
done = true; | |
} | |
} | |
} | |
/** | |
Main | |
**/ | |
function doit(quick, withAem=false) | |
{ | |
var t0,t;t0 =new Date().getTime(); | |
t=new Date().getTime();log(t-t0+" ms");t0=t; | |
log("Starting tests"); | |
flush(); | |
enumerate(); | |
if(quick) _current = QUICK_V; | |
else _current = COMPLETE_V; | |
//Find links | |
var pagelinks = locateLinks(); | |
t =new Date().getTime();log(t-t0+" ms");t0=t; | |
log("Got "+pagelinks.length+" links:"); | |
//Show links | |
//pagelinks.forEach(printArray); | |
//t =new Date().getTime();log(t-t0+" ms");t0=t; | |
//flush(); | |
//Permutate them | |
var s2 = permutate(pagelinks); | |
t =new Date().getTime();log(t-t0+" ms");t0=t; | |
//log("Got "+s2.length+" permutations"); | |
log("From "+pagelinks.length+" links, we generated "+s2.length+" attack vectors"); | |
flush(); | |
t =new Date().getTime();log(t-t0+" ms");t0=t; | |
var start = 0; | |
var end = -1; | |
while( true) | |
{ | |
var v = prompt("There are "+s2.length+" requests to be made\n" | |
+"Show requests: 's'\n" | |
+"Show all found links: 'l'\n" | |
+"Start from index: <0-"+(+s2.length-1)+">\n" | |
+"Run interval <0-"+(+s2.length-2)+"> - <1-"+(s2.length-1)+">\n" | |
+"Or just hit enter to begin "); | |
if(v == null) return; //User pressed cancel | |
if("s" == v.toString().toLowerCase()) | |
{ | |
s2.forEach(showArray); | |
flush(); | |
continue; | |
} | |
if("v" == v.toString().toLowerCase()) | |
{ | |
pagelinks.forEach(printArray); | |
flush(); | |
continue; | |
} | |
var interval = v.toString().split("-"); | |
if(interval != null && interval.length == 2) | |
{ | |
start = Number(interval[0]); | |
end = Number(interval[1])+1; | |
} | |
else | |
{ | |
start = Number(v); | |
} | |
if(isNaN(start)) continue; | |
break; | |
} | |
if(end > -1) | |
s2 = s2.slice(start,end); | |
else | |
s2 = s2.slice(start); | |
fetchSequential(s2.reverse(),start-1); | |
} | |
/** | |
Fetch, in sequential order, all urls in array. | |
**/ | |
function fetchSequential(array,index) | |
{ | |
if(abort) | |
{ | |
log("User aborted"); | |
abort=false; | |
return; | |
} | |
selfreference = arguments.callee | |
index++; | |
var first = array.pop(); | |
if(first == null) | |
{ | |
log("Tests done"); | |
return; | |
} | |
var url = first["url"]; | |
var vector = first["vector"]; | |
log("Fetching "+url +" for test " +vector["desc"]); | |
try | |
{ | |
var rq = new XMLHttpRequest(); | |
rq.vector = vector; | |
rq.activeurl = url; | |
rq.batch = array; | |
rq.index = index; | |
rq.fetch = selfreference; | |
rq.onreadystatechange = function() | |
{ | |
if (rq.readyState == 4 ) | |
{ | |
if(rq.status == 200 || rq.responseText) | |
{ | |
success(rq); | |
}else | |
{ | |
failed(rq); | |
} | |
flush(); | |
} | |
}; | |
rq.open("GET", url, true); | |
rq.send(null); | |
}catch(e){failedEx(index,url,e);} | |
} | |
function failed(request) | |
{ | |
//try{ | |
var vector = request.vector; | |
var url = request.activeurl; | |
var batch = request.batch; | |
report(request.index+separator + request.status+ separator +request.activeurl); | |
var fetch = request.fetch; | |
fetch(batch,request.index); | |
//}catch(e){alert(e);} | |
} | |
function failedEx(index,url,e) | |
{ | |
log("failedEx"); | |
report(index+separator +url+" failed with exception "); | |
if(e) report(e); | |
flush(); | |
} | |
function link(url) | |
{ | |
return "<a href="+url+">"+url+"</a>"; | |
} | |
function success(request) | |
{ | |
var url = request.activeurl; | |
var batch = request.batch; | |
var vector = request.vector; | |
var content = request.responseText; | |
var result = vector.test(content); | |
var fetch = request.fetch; | |
if(result.hit) | |
{ | |
scream(request.index+separator +result.message +separator +url,url); | |
}else | |
{ | |
report(request.index+separator +separator + url); | |
} | |
//catch(e){alert(e);throw e} | |
fetch(batch,request.index); | |
} | |
GM_registerMenuCommand("Quick and Enumerate", function(){doit(true, true)}); | |
GM_registerMenuCommand("Quick XSS Test", function(){doit(true)}); | |
GM_registerMenuCommand("Full XSS Test", function(){doit(false)}); | |
GM_registerMenuCommand("Abort Running Test", function(){abort=true}); | |
GM_registerMenuCommand("Bruteforce", function(){bruteforce()}); | |
GM_registerMenuCommand("Close All", function(){closeConsoles()}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment