Skip to content

Instantly share code, notes, and snippets.

@thomashartm
Last active April 30, 2017 16:49
Show Gist options
  • Save thomashartm/97dc9865b531f1d682842aa20ba621a6 to your computer and use it in GitHub Desktop.
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
// ==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